/*****************************************************************
Copyright (c) 2011 - 2017 Qualcomm Technologies International, Ltd.

PROJECT
    source

FILE NAME
    source_audio.c

DESCRIPTION
    Handles audio routing.

*/


/* header for this file */
#include "source_audio.h"
/* application header files */
#include "source_app_msg_handler.h"
#include "source_debug.h"
#include "source_memory.h"
#include "source_private.h"
#include "source_usb.h"
#include "source_ps.h"
#include "source_aghfp.h"
#include "source_led_handler.h"
#include "source_connection_mgr.h"
#include "source_app_msg_handler.h"
#include "source_button_handler.h"
#include <logging.h>
#include <audio_instance.h>
#include <tws_packetiser.h>
/* profile/library headers */
#include <gain_utils.h>
#include <cap_id_prim.h>
#include <print.h>
#include <util.h>
#include <stdlib.h>
#include <logging.h>
#include <chain.h>
#include <vmal.h>
#include <sink.h>
#include <source.h>
#include <operator.h>
#include <operators.h>
#ifdef DEBUG_AUDIO
    #define AUDIO_DEBUG(x) SOURCE_DEBUG(x)
#else
    #define AUDIO_DEBUG(x)
#endif

/** The number of microseconds per millisecond. */
#define TTP_LATENCY_IN_US(latency_in_ms) (latency_in_ms * US_PER_MS)

#define MAX_NUM_OPERATORS                  10
#define KYMERA_DB_SCALE                    (60)
#define KYMERA_CONFIG_PROMPT_VOLUME        (-10)
#define MSG_SILENCE_DETECTION              0
#define MSG_DELAY_DISCONNECT_AUDIO         1
#define NUMBER_OF_PARAMS                   14
#define AUDIO_SILENCE_CHECK_TIME           40

#define ADAPT_TO_NEW_RATE                  (0x1000)
#define NEW_DECODER_REQUIRED               (0x1001)
#define NEW_DATA_TYPE                      (0x1002)
#define INPUT_RATE_VALID                   (0x1003)
#define NEW_CHANNEL_STATUS                 (0x1004)

#define OPERATOR_MSG_ID                    (2)
#define ADAPT_TO_NEW_RATE_MSG_SAMPLE_RATE  (4)

#define CAP_ID_DOWNLOAD_SILENCE_DETECT      0x40b5
#define KYMERA_OP_MSG_WORD_MSG_ID           1
#define ID_SILENCE_DETECT_EVENT_TRIGGER     0x0c

#define AUDIO_CHAIN_UPDATE_EVENT_TIME       500

typedef enum
{
    A2DP_USB_RX_OP,
    A2DP_USB_SLIENCE_DETECT_OP,
    A2DP_USB_IIR_RESAMPLER_OP,
    A2DP_USB_VOL_CTRL_OP,
    A2DP_USB_ENCODER_OP,
    A2DP_USB_BUFFER_OP,
    A2DP_USB_SPC_OP,
    A2DP_USB_SPLITTER_OP,
    A2DP_USB_NUM_OPERATORS
}usb_chain_op_ids;

typedef enum
{
    A2DP_AUX_TPP_OP,
    A2DP_AUX_RESAMPLER_OP,
    A2DP_AUX_VOL_CTRL_OP,
    A2DP_AUX_ENCODER_OP,
    A2DP_AUX_BUFFER_OP,
    A2DP_AUX_SPC_OP,
    A2DP_AUX_SPLITTER_OP,
    A2DP_AUX_NUM_OPERATORS
}aux_chain_op_ids;

typedef enum
{
    A2DP_I2S_TPP_OP,
    A2DP_I2S_RESAMPLER_OP,
    A2DP_I2S_VOL_CTRL_OP,
    A2DP_I2S_ENCODER_OP,
    A2DP_I2S_BUFFER_OP,
    A2DP_I2S_SPC_OP,
    A2DP_I2S_SPLITTER_OP,
    A2DP_I2S_NUM_OPERATORS
}i2s_chain_op_ids;

typedef enum
{
    A2DP_SPDIF_DECODE_OP,
    A2DP_SPDIF_TPP_OP,
    A2DP_SPDIF_RESAMPLER_OP,
    A2DP_SPDIF_VOL_CTRL_OP,
    A2DP_SPDIF_ENCODER_OP,
    A2DP_SPDIF_BUFFER_OP,
    A2DP_SPDIF_SPC_OP,
    A2DP_SPDIF_SPLITTER_OP,
    A2DP_SPDIF_NUM_OPERATORS
}spdif_chain_op_ids;

typedef enum
{
    TONE_SBC_DECODER_OP,
    TONE_PCM_BUFFER_OP,
    TONE_NUM_OPERATORS
}tone_chain_op_ids;

typedef enum
{
    A2DP_ENCODER_OP,
    A2DP_BUFFER_OP,
    A2DP_SPC_OP,
    A2DP_NUM_OPERATORS
}a2dp_chain_op_ids;

typedef enum
{
    USB_SCO_TX_USB_AUDIO_RX_OP,
    USB_SCO_TX_MIXER_OP,
    USB_SCO_TX_IIR_RESAMPLER_OP,
    USB_SCO_TX_SOURCE_SYNC_OP,
    USB_SCO_TX_AEC_REF_OP,
    USB_SCO_TX_SPC_OP,
    USB_SCO_TX_WBS_ENC,
    USB_SCO_RX_WBS_DEC,
    USB_SCO_RX_IIR_RESAMPLER_OP,
    USB_SCO_RX_USB_TX_OP,
    USB_SCO_NUM_OPERATORS
}usb_sco_chain_op_ids;

typedef enum
{
    ANALOG_SCO_TX_IIR_RESAMPLER_OP,
    ANALOG_SCO_TX_WBS_ENC_OP,
    ANALOG_SCO_RX_IIR_RESAMPLER_OP,
    ANALOG_SCO_RX_WBS_DEC_OP,
    ANALOG_SCO_NUM_OPERATORS
}analog_sco_chain_op_ids;

typedef struct
{
    uint8 index;
    AUDIO_MEDIA_T  type;                        /* media type */
    uint16 mtu;                                 /* Encode packet size to use */
    uint32 rate;                                /* Additional rate */
    Sink   sink;                                /* Media Sink */
    Source source;                              /* Media Source */
    uint8  seid;
    bool  is_routed;
    uint8  aptx_ad_quality;
    Operator operators[MAX_NUM_OPERATORS];
    tws_packetiser_master_t* tws_packetiser;
    Transform packetiser;
    uint8 codec_data_format;
    uint32 frameTxTime;
    uint32 txTime;
    uint32 seq_num;
    uint32 timestamp;
    uint32 frameCount;
}media_t;

/* structure holding the Audio data */
typedef struct
{
    TaskData taskData;
    media_t a2dp_media[2];
    media_t usb_media;
    media_t aux_media;
    media_t i2s_media;
    media_t spdif_media;
    media_t tone_media;
    media_t sco_media;
    media_t spdif_detect_media;
    media_t *media_focus;
    AUDIO_ROUTED_T audio_routed;
    AUDIO_SOURCE_T input_source;
    uint16 audioPeakCheckTImes;
    uint8 bitpool;
    unsigned audio_usb_active:1;
    unsigned audio_a2dp_connection_delay:1;
    unsigned audio_aghfp_connection_delay:1;
    unsigned audio_voip_music_mode:2;
    unsigned audioSilence:1;
    Operator sco_chain[USB_SCO_NUM_OPERATORS];
}AUDIO_DATA_T;

static const operator_setup_item_t usb_sco_tx_spc_setup_items[] =
{
    OPERATORS_SETUP_SWITCHED_PASSTHROUGH_FORMAT(spc_op_format_pcm)
};

source_sync_sink_group_t sco_tx_source_sync_sink_config =
{
    .meta_data_required = FALSE,
    .rate_match = TRUE,
    .channel_mask = CHANNEL_TO_MASK(0) //Channel 0
};

source_sync_source_group_t sco_tx_source_sync_source_config =
{
    .meta_data_required = TRUE,
    .ttp_required = FALSE,
    .channel_mask = CHANNEL_TO_MASK(0)
};

source_sync_route_t sco_tx_source_sync_route_config =
{
    .gain               = GAIN_UNITY,
    .input_terminal     = 0,
    .output_terminal    = 0,
    .sample_rate        = 16000,
    .transition_samples = ROUTE_TRANSITION_INSTANT
};

const char *const toneFiles[] = { "step.sbc", "limit.sbc"};

static operator_setup_t usb_sco_tx_spc_setup = MAKE_OPERATOR_SETUP(usb_sco_tx_spc_setup_items);
static const char download_aptx_encoder[] = "download_aptx_encode.edkcs";
static const char download_aptx_hd_encoder[] = "download_aptxhd_encode.edkcs";
static const char download_aptx_low_latency_encoder[] = "download_aptx_low_latency_encode.edkcs";
static const char download_aptx_ad_encoder[] = "download_aptx_adaptive_encode.edkcs";
/* Get audio plugin functions */
static AUDIO_DATA_T AUDIO_RUNDATA;
/***************************************************************************
Functions
****************************************************************************
*/

static void audio_configure_packetiser(Source source, Sink sink, media_t *media, a2dp_codec_settings *codec_settings, bool airpods_pro2, bool dual_stream);
static void spdif_msg_handler(uint16 *spdifMessage);
static void usb_audio_silence_detection(void);
static void audio_internal_a2dp_disconnect(void);

static int32 Kymera_VolDbToGain(int16 volume_in_db)
{
    int32 gain = (-90);
    if (volume_in_db > (-45))
    {
        gain = volume_in_db;
        if(gain > 0)
        {
            gain = 0;
        }
    }
    return (gain * KYMERA_DB_SCALE);
}

static void spdif_msg_handler(uint16 *spdifMessage)
{
    uint16 messageId = spdifMessage[OPERATOR_MSG_ID];

    switch(messageId)
    {
        case NEW_CHANNEL_STATUS:
        {
            AUDIO_DEBUG(("SPDIF DRIVER: [0x%4x] NEW_CHANNEL_STATUS\n", messageId));
            break;
        }

        case ADAPT_TO_NEW_RATE:
        {
            uint32 sampleRate = (uint32)(spdifMessage[ADAPT_TO_NEW_RATE_MSG_SAMPLE_RATE] * 25);

            AUDIO_DEBUG(("SPDIF DRIVER: [0x%4x] ADAPT_TO_NEW_RATE, sample rate: %d\n", messageId, sampleRate));
            if(AUDIO_RUNDATA.input_source == AUDIO_SOURCE_SPDIF && AUDIO_RUNDATA.spdif_media.is_routed)
            {
                OperatorsSpdifSetOutputSampleRate(AUDIO_RUNDATA.spdif_media.operators[A2DP_SPDIF_DECODE_OP], sampleRate);

                OperatorsStandardSetSampleRate(AUDIO_RUNDATA.spdif_media.operators[A2DP_SPDIF_TPP_OP], sampleRate);

                OperatorsResamplerSetConversionRate(AUDIO_RUNDATA.spdif_media.operators[A2DP_SPDIF_RESAMPLER_OP], sampleRate, AUDIO_RUNDATA.spdif_media.rate);
            }
            break;
        }

        case INPUT_RATE_VALID:
        {
            AUDIO_DEBUG(("SPDIF DRIVER: [0x%4x] INPUT_RATE_VALID rate = %d\n", messageId, spdifMessage[ADAPT_TO_NEW_RATE_MSG_SAMPLE_RATE] * 25));
            break;
        }

        case NEW_DECODER_REQUIRED:
        {
            AUDIO_DEBUG(("SPDIF DRIVER: [0x%4x] NEW_DECODER_REQUIRED\n", messageId));
            break;
        }

        case NEW_DATA_TYPE:
        {
            AUDIO_DEBUG(("SPDIF DRIVER: [0x%4x] NEW_DATA_TYPE\n", messageId));
            break;
        }

        default:
        {
            AUDIO_DEBUG(("SPDIF DRIVER: [0x%4x] Unsupported message!\n", messageId));
            break;
        }
    }
}

static void usb_audio_silence_detection(void)
{
    get_status_data_t* operator_status = NULL;
    uint16 ch0Peak = 0, ch1Peak = 0;

    if((AUDIO_RUNDATA.input_source == AUDIO_SOURCE_USB) &&
       (AUDIO_RUNDATA.audio_routed = AUDIO_ROUTED_A2DP))
    {
        operator_status = OperatorsCreateGetStatusData(NUMBER_OF_PARAMS);

        OperatorsGetStatus(AUDIO_RUNDATA.usb_media.operators[A2DP_USB_SLIENCE_DETECT_OP], operator_status);

        if(operator_status->result == 0)
        {
            ch0Peak = (uint16)(operator_status->value[1] >> 16);
            ch1Peak = (uint16)(operator_status->value[2] >> 16);

            if(AUDIO_RUNDATA.audioSilence)
            {
                if((ch0Peak > 1) || (ch1Peak > 1))
                {
                    AUDIO_RUNDATA.audioPeakCheckTImes += 1;
                }
                else
                {
                    AUDIO_RUNDATA.audioPeakCheckTImes = 0;
                }

                if(AUDIO_RUNDATA.audioPeakCheckTImes > AUDIO_SILENCE_CHECK_TIME)
                {
                    DEBUG_LOG_INFO("Audio Active");
                    AUDIO_RUNDATA.audioSilence = FALSE;
                    MessageSend(app_get_instance(), APP_AUDIO_SUSPEND, 0);
                    MessageSendLater(app_get_instance(), APP_AUDIO_START, 0, 500);
                }
            }
            else
            {
                if((ch0Peak < 2) && (ch1Peak < 2))
                {
                    AUDIO_RUNDATA.audioPeakCheckTImes += 1;
                }
                else
                {
                    AUDIO_RUNDATA.audioPeakCheckTImes = 0;
                }

                if(AUDIO_RUNDATA.audioPeakCheckTImes > AUDIO_SILENCE_CHECK_TIME)
                {
                    DEBUG_LOG_INFO("Audio Silence");
                    AUDIO_RUNDATA.audioSilence = TRUE;
                }
            }
        }

        free(operator_status);

        MessageSendLater(&AUDIO_RUNDATA.taskData, MSG_SILENCE_DETECTION, 0, 5);
    }
}

static void kymeraA2dpSource_ConfigureTtpBufferParams(Operator operator, uint32 min_latency_ms,
                                               uint32 max_latency_ms, uint32 target_latency_ms)
{
    OperatorsStandardSetLatencyLimits(operator, MS_TO_US(min_latency_ms), MS_TO_US(max_latency_ms));
    OperatorsStandardSetTimeToPlayLatency(operator, MS_TO_US(target_latency_ms));
    OperatorsStandardSetBufferSizeWithFormat(operator, TTP_BUFFER_SIZE, operator_data_format_pcm);
}
/****************************************************************************
NAME
    audio_msg_handler -

DESCRIPTION
    Handles messages received from an audio plugin library.

RETURNS
    void
*/
static void audio_msg_handler(Task task, MessageId id, Message message)
{
    UNUSED(task);
    UNUSED(message);

    switch(id)
    {
    case MESSAGE_FROM_OPERATOR:
        //AUDIO_DEBUG(("MESSAGE_FROM_OPERATOR\n"));
        spdif_msg_handler((uint16*)message);
        break;

    case MESSAGE_STREAM_DISCONNECT:
        AUDIO_DEBUG(("MESSAGE_STREAM_DISCONNECT\n"));
        audio_tone_stop();
        break;

    case MESSAGE_MORE_DATA:
        break;

    case MESSAGE_MORE_SPACE:
        break;

    case MSG_SILENCE_DETECTION:
        usb_audio_silence_detection();
        break;

    case MSG_DELAY_DISCONNECT_AUDIO:
        audio_internal_a2dp_disconnect();
        break;

    default:
        AUDIO_DEBUG(("audio_msg_handler id = %d\n", id));
        break;
    }
}

static void tone_chain_connect(media_t *audioSourceMedia, media_t *toneMedia, const char *toneFileName)
{
    AUDIO_DEBUG(("AUDIO: tone_chain_connect\n"));

    if(audioSourceMedia && toneMedia)
    {
        toneMedia->source = PanicNull(StreamFileSource(FileFind(FILE_ROOT, toneFileName, (uint16)strlen(toneFileName))));

        MessageStreamTaskFromSource(toneMedia->source, &AUDIO_RUNDATA.taskData);

        toneMedia->operators[TONE_SBC_DECODER_OP] = PanicZero(OperatorsCreate(CAP_ID_SBC_DECODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        toneMedia->operators[TONE_PCM_BUFFER_OP]  = PanicZero(OperatorsCreate(CAP_ID_BASIC_PASS, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));

        /* Config iir_resampler */
        OperatorsStandardSetBufferSize(toneMedia->operators[TONE_PCM_BUFFER_OP], 1440);

        PanicNull(StreamConnect(toneMedia->source,
                                StreamSinkFromOperatorTerminal(toneMedia->operators[TONE_SBC_DECODER_OP], 0)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(toneMedia->operators[TONE_SBC_DECODER_OP],0),
                                StreamSinkFromOperatorTerminal(toneMedia->operators[TONE_PCM_BUFFER_OP], 0)));

        /* Set tone/prompt volume */
        switch(audioSourceMedia->type)
        {
        case AUDIO_MEDIA_TYPE_USB:
            PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(toneMedia->operators[TONE_PCM_BUFFER_OP],0),
                                    StreamSinkFromOperatorTerminal(audioSourceMedia->operators[A2DP_USB_VOL_CTRL_OP], 1)));

            OperatorsVolumeSetAuxGain(audioSourceMedia->operators[A2DP_USB_VOL_CTRL_OP], KYMERA_DB_SCALE * 10);
            break;
        case AUDIO_MEDIA_TYPE_ANALOG:
            PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(toneMedia->operators[TONE_PCM_BUFFER_OP],0),
                                    StreamSinkFromOperatorTerminal(audioSourceMedia->operators[A2DP_AUX_VOL_CTRL_OP], 1)));

            OperatorsVolumeSetAuxGain(audioSourceMedia->operators[A2DP_AUX_VOL_CTRL_OP], KYMERA_DB_SCALE * 10);
            break;
        case AUDIO_MEDIA_TYPE_I2S:
            PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(toneMedia->operators[TONE_PCM_BUFFER_OP],0),
                                    StreamSinkFromOperatorTerminal(audioSourceMedia->operators[A2DP_I2S_VOL_CTRL_OP], 1)));

            OperatorsVolumeSetAuxGain(audioSourceMedia->operators[A2DP_I2S_VOL_CTRL_OP], KYMERA_DB_SCALE * 10);
            break;
        case AUDIO_MEDIA_TYPE_SPDIF:
            PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(toneMedia->operators[TONE_PCM_BUFFER_OP],0),
                                    StreamSinkFromOperatorTerminal(audioSourceMedia->operators[A2DP_SPDIF_VOL_CTRL_OP], 1)));

            OperatorsVolumeSetAuxGain(audioSourceMedia->operators[A2DP_SPDIF_VOL_CTRL_OP], KYMERA_DB_SCALE * 10);
            break;
        default:
            break;
        }

        /* Finally start the operators */
        PanicFalse(OperatorStartMultiple(TONE_NUM_OPERATORS, toneMedia->operators, NULL));

        toneMedia->is_routed = TRUE;
    }
}

static void usb_chain_connect(media_t *media, bool dual_stream_output, a2dp_codec_settings *codec_settings)
{
    DEBUG_LOG_INFO("AUDIO: usb_chain_connect media_rate=%d", codec_settings->rate);

    if(media)
    {
       /* permit audio to stream */
       OperatorFrameworkEnable(1);

       media->rate      = codec_settings->rate;
       media->type      = AUDIO_MEDIA_TYPE_USB;
       media->is_routed = TRUE;
       AUDIO_RUNDATA.audioSilence = FALSE;

       /****************************************************************************************************/
       /*                                    Create Chain Operators                                        */
       /****************************************************************************************************/
       media->operators[A2DP_USB_RX_OP]            = PanicZero(OperatorsCreate(CAP_ID_USB_AUDIO_RX, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
       media->operators[A2DP_USB_SLIENCE_DETECT_OP] = PanicZero(OperatorsCreate(CAP_ID_BASIC_PASS, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
       media->operators[A2DP_USB_IIR_RESAMPLER_OP] = PanicZero(OperatorsCreate(CAP_ID_IIR_RESAMPLER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
       media->operators[A2DP_USB_VOL_CTRL_OP]      = PanicZero(OperatorsCreate(CAP_ID_VOL_CTRL_VOL, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
       media->operators[A2DP_USB_BUFFER_OP]        = PanicZero(OperatorsCreate(CAP_ID_BASIC_PASS, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
       media->operators[A2DP_USB_SPLITTER_OP]      = PanicZero(OperatorsCreate(CAP_ID_SPLITTER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
       media->operators[A2DP_USB_SPC_OP]           = PanicZero(OperatorsCreate(CAP_ID_SWITCHED_PASSTHROUGH_CONSUMER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));

       switch(codec_settings->seid)
       {
       case  A2DP_SEID_SBC:
           media->operators[A2DP_USB_ENCODER_OP]    = PanicZero(OperatorsCreate(CAP_ID_SBC_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
           break;
       case A2DP_SEID_APTX:
           media->operators[A2DP_USB_ENCODER_OP]    = PanicZero(OperatorsCreate(CAP_ID_DOWNLOAD_APTX_LOW_LATENCY_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
           break;
       case A2DP_SEID_APTX_LOW_LATENCY:
           media->operators[A2DP_USB_ENCODER_OP]    = PanicZero(OperatorsCreate(CAP_ID_DOWNLOAD_APTX_CLASSIC_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
           break;
       case A2DP_SEID_APTXHD:
           media->operators[A2DP_USB_ENCODER_OP]    = PanicZero(OperatorsCreate(CAP_ID_DOWNLOAD_APTXHD_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
           break;
       case A2DP_SEID_APTXAD:
           media->operators[A2DP_USB_ENCODER_OP]    = PanicZero(OperatorsCreate(CAP_ID_DOWNLOAD_APTX_ADAPTIVE_ENCODE, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
           break;
       default:
           Panic();
       }
       /****************************************************************************************************/
       /*                                    Configure Operators                                           */
       /****************************************************************************************************/
       /* Configure the operators */
       /* Config USB Rx           */
       usb_config_t usb_rx_config;
       usb_rx_config.number_of_channels = 2;
       usb_rx_config.sample_rate        = 48000;
       usb_rx_config.sample_size        = 2;
       OperatorsConfigureUsbAudio(media->operators[A2DP_USB_RX_OP], usb_rx_config);

        switch(codec_settings->seid)
        {
            case A2DP_SEID_SBC:
            case A2DP_SEID_APTX:
            case A2DP_SEID_APTXHD:
            {
                kymeraA2dpSource_ConfigureTtpBufferParams(media->operators[A2DP_USB_RX_OP], 140, 160, 150);
            }
            break;
            case A2DP_SEID_APTX_LOW_LATENCY:
            {
                kymeraA2dpSource_ConfigureTtpBufferParams(media->operators[A2DP_USB_RX_OP], 35, 55, 45);
            }
            break;
            case A2DP_SEID_APTXAD:
            {
                if(codec_settings->codecData.aptx_ad_params.features & aptx_ad_twm_support)
                {
                    kymeraA2dpSource_ConfigureTtpBufferParams(media->operators[A2DP_USB_RX_OP], 45, 75, 65);
                }
                else
                {
                    kymeraA2dpSource_ConfigureTtpBufferParams(media->operators[A2DP_USB_RX_OP], 35, 55, 45);
                }
            }
            break;
            default:
            {
                Panic();
            }
        }

       /* Configure basic_passthrough_buffer */
       OperatorsSetPassthroughDataFormat(media->operators[A2DP_USB_SLIENCE_DETECT_OP], operator_data_format_pcm);

       /* Config iir_resampler */
       OperatorsResamplerSetConversionRate(media->operators[A2DP_USB_IIR_RESAMPLER_OP], 48000, media->rate);

       /* Configure basic_passthrough_buffer */
       OperatorsSetPassthroughDataFormat(media->operators[A2DP_USB_BUFFER_OP], operator_data_format_encoded);
       OperatorsStandardSetBufferSize(media->operators[A2DP_USB_BUFFER_OP], 1440);

       /* Configure volume control */
       OperatorsVolumeSetMainGain(media->operators[A2DP_USB_VOL_CTRL_OP], 0);

       if(usbtHostSpeakerIsMute())
       {
           OperatorsVolumeMute(media->operators[A2DP_USB_VOL_CTRL_OP], TRUE);
       }
       else
       {
           OperatorsVolumeMute(media->operators[A2DP_USB_VOL_CTRL_OP], FALSE);
       }

       /* Configure splitter */
       OperatorsConfigureSplitter(media->operators[A2DP_USB_SPLITTER_OP], 2048, dual_stream_output, operator_data_format_encoded);

       /* Configure switch passthrough */
       OperatorsSetSwitchedPassthruEncoding(media->operators[A2DP_USB_SPC_OP], spc_op_format_encoded);
       OperatorsSetSwitchedPassthruMode(media->operators[A2DP_USB_SPC_OP], spc_op_mode_passthrough);

       /* Configure encoder */
       if(codec_settings->seid == A2DP_SEID_SBC)
       {
            sbc_encoder_params_t sbc_encoder_params;

            sbc_encoder_params.channel_mode       = codec_settings->channel_mode;
            sbc_encoder_params.bitpool_size       = codec_settings->codecData.bitpool;
            sbc_encoder_params.sample_rate        = codec_settings->rate;
            sbc_encoder_params.number_of_subbands = (codec_settings->codecData.format & 1) ? 8 : 4;;
            sbc_encoder_params.number_of_blocks   = (((codec_settings->codecData.format >> 4) & 3) + 1) * 4;
            sbc_encoder_params.allocation_method  = (codec_settings->codecData.format & 0x02)? sbc_encoder_allocation_method_snr:sbc_encoder_allocation_method_loudness;

            OperatorsSbcEncoderSetEncodingParams(media->operators[A2DP_USB_ENCODER_OP], &sbc_encoder_params);
       }
       else if(codec_settings->seid == A2DP_SEID_APTXAD)
       {
            aptxad_encoder_params_t aptxad_encoder_params;
            /*
            * Workaround for issue found with some older Sinks that negotiated
            * JOINT_STEREO and advertised aptX Adaptive v2.0 but only worked
            * properly with aptX Adaptive v1.0
            */
            if (codec_settings->channel_mode == a2dp_joint_stereo &&
                codec_settings->codecData.aptx_ad_params.version == aptx_ad_version_2_0)
            {
               /* A2DP joint stereo workaround applied, using aptX Adaptive v1.1*/;
               aptxad_encoder_params.compatibility = aptx_ad_version_1_1;
            }
            else  /* use the aptX Adaptive version that was negotiated */
            {
               aptxad_encoder_params.compatibility = codec_settings->codecData.aptx_ad_params.version;
            }

            if (aptxad_encoder_params.compatibility == aptx_ad_version_unknown)
            {
               /* Try 2.1 */
               aptxad_encoder_params.compatibility = aptx_ad_version_2_1;
            }
            printf("aptxad_encoder_params.compatibility=%d\n", aptxad_encoder_params.compatibility);
            aptxad_encoder_params.bitrate = 279;
            aptxad_encoder_params.dh5_dh3 = 352;
            aptxad_encoder_params.quality = APTX_PROFILE_LOW_LATENCY;
            aptxad_encoder_params.channel = APTX_CHANNEL_STEREO; /* stereo by default */
            aptxad_encoder_params.sample_rate = codec_settings->rate;
            OperatorsAptxAdEncoderSetEncodingParams(media->operators[A2DP_USB_ENCODER_OP], &aptxad_encoder_params);
       }

       /****************************************************************************************************/
       /*                                    Connect Chain                                                 */
       /****************************************************************************************************/
       PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_RX_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_IIR_RESAMPLER_OP], 0)));
       PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_RX_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_IIR_RESAMPLER_OP], 1)));

       PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_IIR_RESAMPLER_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_VOL_CTRL_OP], 0)));
       PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_IIR_RESAMPLER_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_VOL_CTRL_OP], 2)));

       PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_VOL_CTRL_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_SLIENCE_DETECT_OP], 0)));
       PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_VOL_CTRL_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_SLIENCE_DETECT_OP], 1)));

       PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_SLIENCE_DETECT_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_ENCODER_OP], 0)));
       PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_SLIENCE_DETECT_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_ENCODER_OP], 1)));

       PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_ENCODER_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_BUFFER_OP], 0)));

       PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_BUFFER_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_SPC_OP], 0)));

       PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_SPC_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_SPLITTER_OP], 0)));

       Source pc_source = StreamUsbEndPointSource(end_point_iso_in);
       Sink usb_rx_sink = StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_RX_OP], 0);

       StreamDisconnect(pc_source, NULL);
       StreamDisconnect(NULL, usb_rx_sink);

       StreamConnect(pc_source, usb_rx_sink);

       MessageSendLater(&AUDIO_RUNDATA.taskData, MSG_SILENCE_DETECTION, 0, 100);
    }
}

static void aux_chain_connect(media_t *media, bool dual_stream_output, a2dp_codec_settings *codec_settings)
{
    DEBUG_LOG_INFO("AUDIO: aux_chain_connect");

    if(media)
    {
        /* permit audio to stream */
        OperatorFrameworkEnable(1);

        media->rate      = codec_settings->rate;
        media->type      = AUDIO_MEDIA_TYPE_ANALOG;
        media->is_routed = TRUE;

        /****************************************************************************************************/
        /*                                    Create Chain Operators                                        */
        /****************************************************************************************************/
        media->operators[A2DP_AUX_TPP_OP]       = PanicZero(OperatorsCreate(CAP_ID_TTP_PASS, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_AUX_RESAMPLER_OP] = PanicZero(OperatorsCreate(CAP_ID_IIR_RESAMPLER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_AUX_VOL_CTRL_OP]  = PanicZero(OperatorsCreate(CAP_ID_VOL_CTRL_VOL, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_AUX_BUFFER_OP]    = PanicZero(OperatorsCreate(CAP_ID_BASIC_PASS, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_AUX_SPC_OP]       = PanicZero(OperatorsCreate(CAP_ID_SWITCHED_PASSTHROUGH_CONSUMER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_AUX_SPLITTER_OP]  = PanicZero(OperatorsCreate(CAP_ID_SPLITTER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));

        switch(codec_settings->seid)
        {
        case A2DP_SEID_SBC:
            media->operators[A2DP_AUX_ENCODER_OP]  = PanicZero(OperatorsCreate(CAP_ID_SBC_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
            break;
        case A2DP_SEID_APTX:
            media->operators[A2DP_AUX_ENCODER_OP]  = PanicZero(OperatorsCreate(CAP_ID_DOWNLOAD_APTX_CLASSIC_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
            break;
        case A2DP_SEID_APTX_LOW_LATENCY:
            media->operators[A2DP_AUX_ENCODER_OP]  = PanicZero(OperatorsCreate(CAP_ID_DOWNLOAD_APTX_LOW_LATENCY_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
            break;
        case A2DP_SEID_APTXHD:
            media->operators[A2DP_AUX_ENCODER_OP]  = PanicZero(OperatorsCreate(CAP_ID_DOWNLOAD_APTXHD_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
            break;
        case A2DP_SEID_APTXAD:
            media->operators[A2DP_AUX_ENCODER_OP]  = PanicZero(OperatorsCreate(CAP_ID_DOWNLOAD_APTX_ADAPTIVE_ENCODE, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
            break;
        default:
            Panic();
        }
        /****************************************************************************************************/
        /*                                    Configure Operators                                           */
        /****************************************************************************************************/
        /* Configure the operators */
        /* Configure ttp passthrough */
        switch(codec_settings->seid)
        {
            case A2DP_SEID_SBC:
            case A2DP_SEID_APTX:
            case A2DP_SEID_APTXHD:
            {
                OperatorsStandardSetLatencyLimits(media->operators[A2DP_AUX_TPP_OP], TTP_LATENCY_IN_US(140), TTP_LATENCY_IN_US(160));
                OperatorsConfigureTtpPassthrough(media->operators[A2DP_AUX_TPP_OP], TTP_LATENCY_IN_US(150), 48000, operator_data_format_pcm);
                OperatorsStandardSetBufferSizeWithFormat(media->operators[A2DP_AUX_TPP_OP], TTP_BUFFER_SIZE, operator_data_format_pcm);
            }
            break;
            case A2DP_SEID_APTX_LOW_LATENCY:
            {
                OperatorsStandardSetLatencyLimits(media->operators[A2DP_AUX_TPP_OP], TTP_LATENCY_IN_US(35), TTP_LATENCY_IN_US(55));
                OperatorsConfigureTtpPassthrough(media->operators[A2DP_AUX_TPP_OP], TTP_LATENCY_IN_US(45), 48000, operator_data_format_pcm);
                OperatorsStandardSetBufferSizeWithFormat(media->operators[A2DP_AUX_TPP_OP], TTP_BUFFER_SIZE, operator_data_format_pcm);
            }
            break;
            case A2DP_SEID_APTXAD:
            {
                if(codec_settings->codecData.aptx_ad_params.features & aptx_ad_twm_support)
                {
                    OperatorsStandardSetLatencyLimits(media->operators[A2DP_AUX_TPP_OP], TTP_LATENCY_IN_US(45), TTP_LATENCY_IN_US(75));
                    OperatorsConfigureTtpPassthrough(media->operators[A2DP_AUX_TPP_OP], TTP_LATENCY_IN_US(65), 48000, operator_data_format_pcm);
                    OperatorsStandardSetBufferSizeWithFormat(media->operators[A2DP_AUX_TPP_OP], TTP_BUFFER_SIZE, operator_data_format_pcm);
                }
                else
                {
                    OperatorsStandardSetLatencyLimits(media->operators[A2DP_AUX_TPP_OP], TTP_LATENCY_IN_US(35), TTP_LATENCY_IN_US(55));
                    OperatorsConfigureTtpPassthrough(media->operators[A2DP_AUX_TPP_OP], TTP_LATENCY_IN_US(45), 48000, operator_data_format_pcm);
                    OperatorsStandardSetBufferSizeWithFormat(media->operators[A2DP_AUX_TPP_OP], TTP_BUFFER_SIZE, operator_data_format_pcm);
                }
            }
            break;
            default:
            {
                Panic();
            }
        }

        /* Configure iir resampler */
        OperatorsResamplerSetConversionRate(media->operators[A2DP_AUX_RESAMPLER_OP], 48000, media->rate);

        /* Configure volume control */
        OperatorsVolumeSetMainGain(media->operators[A2DP_AUX_VOL_CTRL_OP], 0);
        OperatorsVolumeMute(media->operators[A2DP_AUX_VOL_CTRL_OP], FALSE);

        /* Configure basic_passthrough_buffer */
        OperatorsSetPassthroughDataFormat(media->operators[A2DP_AUX_BUFFER_OP], operator_data_format_encoded);
        OperatorsStandardSetBufferSize(media->operators[A2DP_AUX_BUFFER_OP], 1440);

        /* Configure switch passthrough */
        OperatorsSetSwitchedPassthruEncoding(media->operators[A2DP_AUX_SPC_OP], spc_op_format_encoded);
        OperatorsSetSwitchedPassthruMode(media->operators[A2DP_AUX_SPC_OP], spc_op_mode_passthrough);

        /* Configure splitter */
        OperatorsConfigureSplitter(media->operators[A2DP_AUX_SPLITTER_OP], 2048, dual_stream_output, operator_data_format_encoded);

        /* Configure encoder */
        switch(codec_settings->seid)
        {
        case  A2DP_SEID_SBC:
        {
            sbc_encoder_params_t sbc_encoder_params;

             sbc_encoder_params.channel_mode       = codec_settings->channel_mode;
             sbc_encoder_params.bitpool_size       = codec_settings->codecData.bitpool;
             sbc_encoder_params.sample_rate        = codec_settings->rate;
             sbc_encoder_params.number_of_subbands = (codec_settings->codecData.format & 1) ? 8 : 4;;
             sbc_encoder_params.number_of_blocks   = (((codec_settings->codecData.format >> 4) & 3) + 1) * 4;
             sbc_encoder_params.allocation_method  = (codec_settings->codecData.format & 0x02)? sbc_encoder_allocation_method_snr:sbc_encoder_allocation_method_loudness;

             OperatorsSbcEncoderSetEncodingParams(media->operators[A2DP_AUX_ENCODER_OP], &sbc_encoder_params);
        }
        break;
        case A2DP_SEID_APTX:
        case A2DP_SEID_APTX_LOW_LATENCY:
        case A2DP_SEID_APTXHD:
            break;
        case A2DP_SEID_APTXAD:
        {
             aptxad_encoder_params_t aptxad_encoder_params;
             /*
             * Workaround for issue found with some older Sinks that negotiated
             * JOINT_STEREO and advertised aptX Adaptive v2.0 but only worked
             * properly with aptX Adaptive v1.0
             */
             aptxad_encoder_params.compatibility = aptx_ad_version_unknown;

             if ( codec_settings->channel_mode == a2dp_joint_stereo &&
                 codec_settings->codecData.aptx_ad_params.version == aptx_ad_version_2_0)
             {
                /* A2DP joint stereo workaround applied, using aptX Adaptive v1.1*/;
                aptxad_encoder_params.compatibility = aptx_ad_version_1_1;
             }
             else  /* use the aptX Adaptive version that was negotiated */
             {
                aptxad_encoder_params.compatibility = codec_settings->codecData.aptx_ad_params.version;
             }

             if (aptxad_encoder_params.compatibility == aptx_ad_version_unknown)
             {
                /* Try 2.1 */
                aptxad_encoder_params.compatibility = aptx_ad_version_2_1;
             }

             aptxad_encoder_params.bitrate = 279;
             aptxad_encoder_params.dh5_dh3 = 352;
             aptxad_encoder_params.quality = APTX_PROFILE_LOW_LATENCY;
             aptxad_encoder_params.channel = APTX_CHANNEL_STEREO; /* stereo by default */
             aptxad_encoder_params.sample_rate = codec_settings->rate;
             OperatorsAptxAdEncoderSetEncodingParams(media->operators[A2DP_AUX_ENCODER_OP], &aptxad_encoder_params);
        }
        break;
        default:
            Panic();
        }
        /****************************************************************************************************/
        /*                                    Connect Chain                                                 */
        /****************************************************************************************************/
       /* Get the input endpoints */
        Source source_line_in_left  = PanicNull(StreamAudioSource(AUDIO_HARDWARE_CODEC, AUDIO_INSTANCE_0, AUDIO_CHANNEL_A));
        PanicFalse(SourceConfigure(source_line_in_left,  STREAM_CODEC_INPUT_RATE, 48000));
        PanicFalse(SourceConfigure(source_line_in_left,  STREAM_AUDIO_SAMPLE_SIZE, 24));
        PanicFalse(SourceConfigure(source_line_in_left,  STREAM_CODEC_INPUT_GAIN, 12));
        SourceConfigure(source_line_in_left, STREAM_CODEC_MIC_INPUT_GAIN_ENABLE, 0);

        Source source_line_in_right = PanicNull(StreamAudioSource(AUDIO_HARDWARE_CODEC, AUDIO_INSTANCE_0, AUDIO_CHANNEL_B));
        PanicFalse(SourceConfigure(source_line_in_right, STREAM_CODEC_INPUT_RATE, 48000));
        PanicFalse(SourceConfigure(source_line_in_right, STREAM_AUDIO_SAMPLE_SIZE, 24));
        PanicFalse(SourceConfigure(source_line_in_right, STREAM_CODEC_INPUT_GAIN, 12));
        SourceConfigure(source_line_in_right, STREAM_CODEC_MIC_INPUT_GAIN_ENABLE, 0);

        PanicFalse(SourceSynchronise(source_line_in_left,source_line_in_right));

        PanicNull(StreamConnect(source_line_in_left, StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_TPP_OP], 0)));
        PanicNull(StreamConnect(source_line_in_right, StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_TPP_OP], 1)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_TPP_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_RESAMPLER_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_TPP_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_RESAMPLER_OP], 1)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_RESAMPLER_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_VOL_CTRL_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_RESAMPLER_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_VOL_CTRL_OP], 2)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_VOL_CTRL_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_ENCODER_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_VOL_CTRL_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_ENCODER_OP], 1)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_ENCODER_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_BUFFER_OP], 0)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_BUFFER_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_SPC_OP], 0)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_SPC_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_SPLITTER_OP], 0)));
    }
}

static void i2s_chain_connect(media_t *media, bool dual_stream_output, a2dp_codec_settings *codec_settings)
{
    DEBUG_LOG_INFO("AUDIO: i2s_chain_connect");

    if(media)
    {
        /* permit audio to stream */
        OperatorFrameworkEnable(1);

        media->rate      = codec_settings->rate;
        media->type      = AUDIO_MEDIA_TYPE_I2S;
        media->is_routed = TRUE;

        /****************************************************************************************************/
        /*                                    Create Chain Operators                                        */
        /****************************************************************************************************/
        media->operators[A2DP_I2S_TPP_OP]       = PanicZero(OperatorsCreate(CAP_ID_TTP_PASS, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_I2S_RESAMPLER_OP] = PanicZero(OperatorsCreate(CAP_ID_IIR_RESAMPLER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_I2S_VOL_CTRL_OP]  = PanicZero(OperatorsCreate(CAP_ID_VOL_CTRL_VOL, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_I2S_BUFFER_OP]    = PanicZero(OperatorsCreate(CAP_ID_BASIC_PASS, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_I2S_SPC_OP]       = PanicZero(OperatorsCreate(CAP_ID_SWITCHED_PASSTHROUGH_CONSUMER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_I2S_SPLITTER_OP]  = PanicZero(OperatorsCreate(CAP_ID_SPLITTER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));

        switch(codec_settings->seid)
        {
        case A2DP_SEID_SBC:
            media->operators[A2DP_I2S_ENCODER_OP]  = PanicZero(OperatorsCreate(CAP_ID_SBC_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
            break;
        case A2DP_SEID_APTX:
        case A2DP_SEID_APTX_LOW_LATENCY:
            media->operators[A2DP_I2S_ENCODER_OP]  = PanicZero(OperatorsCreate(CAP_ID_DOWNLOAD_APTX_CLASSIC_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
            break;
        case A2DP_SEID_APTXHD:
            media->operators[A2DP_I2S_ENCODER_OP]  = PanicZero(OperatorsCreate(CAP_ID_DOWNLOAD_APTXHD_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
            break;
        default:
            Panic();
        }
        /****************************************************************************************************/
        /*                                    Configure Operators                                           */
        /****************************************************************************************************/
        /* Configure the operators */
        /* Configure ttp passthrough */
        OperatorsStandardSetLatencyLimits(media->operators[A2DP_I2S_TPP_OP], TTP_LATENCY_IN_US(50), TTP_LATENCY_IN_US(150));
        OperatorsConfigureTtpPassthrough(media->operators[A2DP_I2S_TPP_OP], TTP_LATENCY_IN_US(70), media->rate, operator_data_format_pcm);
        OperatorsStandardSetBufferSizeWithFormat(media->operators[A2DP_I2S_TPP_OP], TTP_BUFFER_SIZE, operator_data_format_pcm);

        /* Configure iir resampler */
        OperatorsResamplerSetConversionRate(media->operators[A2DP_I2S_RESAMPLER_OP], 48000, media->rate);

        /* Configure volume control */
        OperatorsVolumeSetMainGain(media->operators[A2DP_I2S_VOL_CTRL_OP], 0);
        OperatorsVolumeMute(media->operators[A2DP_I2S_VOL_CTRL_OP], FALSE);

        /* Configure basic_passthrough_buffer */
        OperatorsSetPassthroughDataFormat(media->operators[A2DP_I2S_BUFFER_OP], operator_data_format_encoded);
        OperatorsStandardSetBufferSize(media->operators[A2DP_I2S_BUFFER_OP], 1440);

        /* Configure switch passthrough */
        OperatorsSetSwitchedPassthruEncoding(media->operators[A2DP_I2S_SPC_OP], spc_op_format_encoded);
        OperatorsSetSwitchedPassthruMode(media->operators[A2DP_I2S_SPC_OP], spc_op_mode_passthrough);

        /* Configure splitter */
        OperatorsConfigureSplitter(media->operators[A2DP_I2S_SPLITTER_OP], 2048, dual_stream_output, operator_data_format_encoded);

        /* Configure encoder */
        switch(codec_settings->seid)
        {
        case  A2DP_SEID_SBC:
        {
            sbc_encoder_params_t sbc_encoder_params;

             sbc_encoder_params.channel_mode       = codec_settings->channel_mode;
             sbc_encoder_params.bitpool_size       = codec_settings->codecData.bitpool;
             sbc_encoder_params.sample_rate        = codec_settings->rate;
             sbc_encoder_params.number_of_subbands = (codec_settings->codecData.format & 1) ? 8 : 4;;
             sbc_encoder_params.number_of_blocks   = (((codec_settings->codecData.format >> 4) & 3) + 1) * 4;
             sbc_encoder_params.allocation_method  = (codec_settings->codecData.format & 0x02)? sbc_encoder_allocation_method_snr:sbc_encoder_allocation_method_loudness;

             OperatorsSbcEncoderSetEncodingParams(media->operators[A2DP_I2S_ENCODER_OP], &sbc_encoder_params);
        }
        break;

        case A2DP_SEID_APTX:
        case A2DP_SEID_APTX_LOW_LATENCY:
        case A2DP_SEID_APTXHD:
            break;
        case A2DP_SEID_APTXAD:
            {
                 aptxad_encoder_params_t aptxad_encoder_params;
                 /*
                 * Workaround for issue found with some older Sinks that negotiated
                 * JOINT_STEREO and advertised aptX Adaptive v2.0 but only worked
                 * properly with aptX Adaptive v1.0
                 */
                 if ( codec_settings->channel_mode == a2dp_joint_stereo &&
                     codec_settings->codecData.aptx_ad_params.version == aptx_ad_version_2_0)
                 {
                    /* A2DP joint stereo workaround applied, using aptX Adaptive v1.1*/;
                    aptxad_encoder_params.compatibility = aptx_ad_version_1_1;
                 }
                 else  /* use the aptX Adaptive version that was negotiated */
                 {
                    aptxad_encoder_params.compatibility = codec_settings->codecData.aptx_ad_params.version;
                 }

                 if (aptxad_encoder_params.compatibility == aptx_ad_version_unknown)
                 {
                    /* Try 2.1 */
                    aptxad_encoder_params.compatibility = aptx_ad_version_2_1;
                 }

                 aptxad_encoder_params.bitrate = 279;
                 aptxad_encoder_params.dh5_dh3 = 352;
                 aptxad_encoder_params.quality = APTX_PROFILE_LOW_LATENCY;
                 aptxad_encoder_params.channel = APTX_CHANNEL_STEREO; /* stereo by default */
                 aptxad_encoder_params.sample_rate = codec_settings->rate;
                 OperatorsAptxAdEncoderSetEncodingParams(media->operators[A2DP_I2S_ENCODER_OP], &aptxad_encoder_params);
            }
            break;
        default:
            Panic();
        }
        /****************************************************************************************************/
        /*                                    Connect Chain                                                 */
        /****************************************************************************************************/
        /* Get the input endpoints */
        /* obtain source to I2S hardware */
        Source lSource_0 = StreamAudioSource(AUDIO_HARDWARE_I2S, AUDIO_INSTANCE_0, AUDIO_CHANNEL_SLOT_0 );
        Source lSource_1 = StreamAudioSource(AUDIO_HARDWARE_I2S, AUDIO_INSTANCE_0, AUDIO_CHANNEL_SLOT_1 );
        /* Ensure both channels are synchronised */
        PanicFalse(SourceSynchronise(lSource_0, lSource_1));
        /* configure the I2S interface operating mode, run in slave mode */
        PanicFalse(SourceConfigure(lSource_0, STREAM_I2S_MASTER_MODE, 1));
        PanicFalse(SourceConfigure(lSource_1, STREAM_I2S_MASTER_MODE, 1));
        /* set the sample rate of the dsp audio data */
        PanicFalse(SourceConfigure(lSource_0, STREAM_I2S_MASTER_CLOCK_RATE, 48000 * 32 * 2));
        PanicFalse(SourceConfigure(lSource_1, STREAM_I2S_MASTER_CLOCK_RATE, 48000 * 32 * 2));
        /* set the sample rate of the dsp audio data */
        PanicFalse(SourceConfigure(lSource_0, STREAM_I2S_SYNC_RATE, 48000));
        PanicFalse(SourceConfigure(lSource_1, STREAM_I2S_SYNC_RATE, 48000));
        /* left justified or i2s data */
        PanicFalse(SourceConfigure(lSource_0, STREAM_I2S_JSTFY_FORMAT, 0));
        PanicFalse(SourceConfigure(lSource_1, STREAM_I2S_JSTFY_FORMAT, 0));
        /* MSB of data occurs on the second SCLK */
        PanicFalse(SourceConfigure(lSource_0, STREAM_I2S_LFT_JSTFY_DLY, 1));
        PanicFalse(SourceConfigure(lSource_1, STREAM_I2S_LFT_JSTFY_DLY, 1));
        /* data is LEFT channel when word clock is high */
        PanicFalse(SourceConfigure(lSource_0, STREAM_I2S_CHNL_PLRTY, 1));
        PanicFalse(SourceConfigure(lSource_1, STREAM_I2S_CHNL_PLRTY, 1));

        PanicFalse(SourceConfigure(lSource_0, STREAM_I2S_JSTFY_RES, 2));
        PanicFalse(SourceConfigure(lSource_1, STREAM_I2S_JSTFY_RES, 2));

        PanicFalse(SourceConfigure(lSource_0, STREAM_I2S_RX_START_SAMPLE, 1));
        PanicFalse(SourceConfigure(lSource_1, STREAM_I2S_RX_START_SAMPLE, 1));

        PanicFalse(SourceConfigure(lSource_0, STREAM_I2S_CROP_ENABLE, 0));
        PanicFalse(SourceConfigure(lSource_1, STREAM_I2S_CROP_ENABLE, 0));

        /* number of data bits per sample, 24 */
        PanicFalse(SourceConfigure(lSource_0, STREAM_I2S_BITS_PER_SAMPLE, 24));
        PanicFalse(SourceConfigure(lSource_1, STREAM_I2S_BITS_PER_SAMPLE, 24));
        /* audio sample size, 24 */
        PanicFalse(SourceConfigure(lSource_0, STREAM_AUDIO_SAMPLE_SIZE, 24));
        PanicFalse(SourceConfigure(lSource_1, STREAM_AUDIO_SAMPLE_SIZE, 24));

        PanicNull(StreamConnect(lSource_0, StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_TPP_OP], 0)));
        PanicNull(StreamConnect(lSource_1, StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_TPP_OP], 1)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_TPP_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_RESAMPLER_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_TPP_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_RESAMPLER_OP], 1)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_RESAMPLER_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_VOL_CTRL_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_RESAMPLER_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_VOL_CTRL_OP], 2)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_VOL_CTRL_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_ENCODER_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_VOL_CTRL_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_ENCODER_OP], 1)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_ENCODER_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_BUFFER_OP], 0)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_BUFFER_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_SPC_OP], 0)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_SPC_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_SPLITTER_OP], 0)));
    }
}

static void spdif_chain_connect(media_t *media, bool dual_stream_output, a2dp_codec_settings *codec_settings)
{
    DEBUG_LOG_INFO("AUDIO: spdif_chain_connect");

    if(media)
    {
        /* permit audio to stream */
        OperatorFrameworkEnable(1);

        media->rate      = codec_settings->rate;
        media->type      = AUDIO_MEDIA_TYPE_SPDIF;
        media->is_routed = TRUE;

        /****************************************************************************************************/
        /*                                    Create Chain Operators                                        */
        /****************************************************************************************************/
        media->operators[A2DP_SPDIF_DECODE_OP]    = PanicZero(OperatorsCreate(CAP_ID_SPDIF_DECODE, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_SPDIF_TPP_OP]       = PanicZero(OperatorsCreate(CAP_ID_TTP_PASS, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_SPDIF_RESAMPLER_OP] = PanicZero(OperatorsCreate(CAP_ID_IIR_RESAMPLER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_SPDIF_VOL_CTRL_OP]  = PanicZero(OperatorsCreate(CAP_ID_VOL_CTRL_VOL, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_SPDIF_BUFFER_OP]    = PanicZero(OperatorsCreate(CAP_ID_BASIC_PASS, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_SPDIF_SPC_OP]       = PanicZero(OperatorsCreate(CAP_ID_SWITCHED_PASSTHROUGH_CONSUMER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        media->operators[A2DP_SPDIF_SPLITTER_OP]  = PanicZero(OperatorsCreate(CAP_ID_SPLITTER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));

        switch(codec_settings->seid)
        {
        case A2DP_SEID_SBC:
            media->operators[A2DP_SPDIF_ENCODER_OP]  = PanicZero(OperatorsCreate(CAP_ID_SBC_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
            break;
        case A2DP_SEID_APTX:
        case A2DP_SEID_APTX_LOW_LATENCY:
            media->operators[A2DP_SPDIF_ENCODER_OP]  = PanicZero(OperatorsCreate(CAP_ID_DOWNLOAD_APTX_CLASSIC_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
            break;
        case A2DP_SEID_APTXHD:
            media->operators[A2DP_SPDIF_ENCODER_OP]  = PanicZero(OperatorsCreate(CAP_ID_DOWNLOAD_APTXHD_ENCODER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
            break;
        default:
            Panic();
        }
        /****************************************************************************************************/
        /*                                    Configure Operators                                           */
        /****************************************************************************************************/
        /* Configure the operators */
        /* Configure the operators */
        MessageOperatorTask(media->operators[A2DP_SPDIF_DECODE_OP], &AUDIO_RUNDATA.taskData);
        /* Configure ttp passthrough */
        OperatorsStandardSetLatencyLimits(media->operators[A2DP_SPDIF_TPP_OP], TTP_LATENCY_IN_US(50), TTP_LATENCY_IN_US(140));
        OperatorsConfigureTtpPassthrough(media->operators[A2DP_SPDIF_TPP_OP], TTP_LATENCY_IN_US(70), media->rate, operator_data_format_pcm);
        OperatorsStandardSetBufferSizeWithFormat(media->operators[A2DP_SPDIF_TPP_OP], TTP_BUFFER_SIZE, operator_data_format_pcm);

        /* Configure iir resampler */
        OperatorsResamplerSetConversionRate(media->operators[A2DP_SPDIF_RESAMPLER_OP], 48000, media->rate);

        /* Configure volume control */
        OperatorsVolumeSetMainGain(media->operators[A2DP_SPDIF_VOL_CTRL_OP], 0);
        OperatorsVolumeMute(media->operators[A2DP_SPDIF_VOL_CTRL_OP], FALSE);

        /* Configure basic_passthrough_buffer */
        OperatorsSetPassthroughDataFormat(media->operators[A2DP_SPDIF_BUFFER_OP], operator_data_format_encoded);
        OperatorsStandardSetBufferSize(media->operators[A2DP_SPDIF_BUFFER_OP], 1440);

        /* Configure switch passthrough */
        OperatorsSetSwitchedPassthruEncoding(media->operators[A2DP_SPDIF_SPC_OP], spc_op_format_encoded);
        OperatorsSetSwitchedPassthruMode(media->operators[A2DP_SPDIF_SPC_OP], spc_op_mode_passthrough);

        /* Configure splitter */
        OperatorsConfigureSplitter(media->operators[A2DP_SPDIF_SPLITTER_OP], 2048, dual_stream_output, operator_data_format_encoded);

        /* Configure encoder */
        switch(codec_settings->seid)
        {
        case  A2DP_SEID_SBC:
        {
            sbc_encoder_params_t sbc_encoder_params;

             sbc_encoder_params.channel_mode       = codec_settings->channel_mode;
             sbc_encoder_params.bitpool_size       = codec_settings->codecData.bitpool;
             sbc_encoder_params.sample_rate        = codec_settings->rate;
             sbc_encoder_params.number_of_subbands = (codec_settings->codecData.format & 1) ? 8 : 4;;
             sbc_encoder_params.number_of_blocks   = (((codec_settings->codecData.format >> 4) & 3) + 1) * 4;
             sbc_encoder_params.allocation_method  = (codec_settings->codecData.format & 0x02)? sbc_encoder_allocation_method_snr:sbc_encoder_allocation_method_loudness;

             OperatorsSbcEncoderSetEncodingParams(media->operators[A2DP_SPDIF_ENCODER_OP], &sbc_encoder_params);
        }
        break;

        case A2DP_SEID_APTX:
        case A2DP_SEID_APTX_LOW_LATENCY:
        case A2DP_SEID_APTXHD:
            break;
        default:
            Panic();
        }
        /****************************************************************************************************/
        /*                                    Connect Chain                                                 */
        /****************************************************************************************************/
        /* Get the input endpoints */
        Source source_spdif_0 = PanicNull(StreamAudioSource(AUDIO_HARDWARE_SPDIF, AUDIO_INSTANCE_0, AUDIO_CHANNEL_A));
        Source source_spdif_1 = PanicNull(StreamAudioSource(AUDIO_HARDWARE_SPDIF, AUDIO_INSTANCE_0, AUDIO_CHANNEL_B));

        PanicFalse(SourceConfigure(source_spdif_0, STREAM_AUDIO_SAMPLE_SIZE, 24));
        PanicFalse(SourceConfigure(source_spdif_0, STREAM_SPDIF_AUTO_RATE_DETECT, TRUE));
        PanicFalse(SourceConfigure(source_spdif_0, STREAM_SPDIF_CHNL_STS_REPORT_MODE, TRUE));

        PanicFalse(SourceConfigure(source_spdif_1, STREAM_AUDIO_SAMPLE_SIZE, 24));
        PanicFalse(SourceConfigure(source_spdif_1, STREAM_SPDIF_AUTO_RATE_DETECT, TRUE));
        PanicFalse(SourceConfigure(source_spdif_1, STREAM_SPDIF_CHNL_STS_REPORT_MODE, TRUE));

        PanicFalse(SourceSynchronise(source_spdif_0,source_spdif_1));

        PanicNull(StreamConnect(source_spdif_0, StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_DECODE_OP], 0)));
        PanicNull(StreamConnect(source_spdif_1, StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_DECODE_OP], 1)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_DECODE_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_TPP_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_DECODE_OP],2), StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_TPP_OP], 1)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_TPP_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_RESAMPLER_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_TPP_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_RESAMPLER_OP], 1)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_RESAMPLER_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_VOL_CTRL_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_RESAMPLER_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_VOL_CTRL_OP], 2)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_VOL_CTRL_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_ENCODER_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_VOL_CTRL_OP],1), StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_ENCODER_OP], 1)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_ENCODER_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_BUFFER_OP], 0)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_BUFFER_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_SPC_OP], 0)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_SPC_OP],0), StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_SPLITTER_OP], 0)));
    }
}

static void audio_configure_packetiser(Source source, Sink sink, media_t *media, a2dp_codec_settings *codec_settings, bool airpods_pro2, bool dual_stream)
{
    AUDIO_DEBUG(("AUDIO: audio_configure_packetiser seid = %d airpods_pro2=%d\n", codec_settings->seid, airpods_pro2));

    if(media)
    {
        media->is_routed = TRUE;
        media->source    = source;
        media->sink      = sink;
        media->seq_num   = 0;
        media->mtu       = codec_settings->codecData.packet_size;
        media->codec_data_format = codec_settings->codecData.format;

        if(!airpods_pro2)
        {
            MessageCancelAll(&AUDIO_RUNDATA.taskData, MSG_SILENCE_DETECTION);
        }

        switch (codec_settings->seid)
        {
            case A2DP_SEID_SBC:
            {
                if(airpods_pro2 && !dual_stream)
                {
                    media->packetiser = NULL;

                    tws_packetiser_master_config_t config;

                    config.source = media->source;
                    config.codec  = TWS_PACKETISER_CODEC_SBC;
                    config.sink   = media->sink;
                    config.client = &AUDIO_RUNDATA.taskData;
                    config.mode   = TWS_PACKETISER_MASTER_MODE_RTP;
                    config.cp_header_enable = codec_settings->codecData.content_protection;
                    config.scmst  = codec_settings->codecData.content_protection ?packetiser_helper_scmst_copy_prohibited : packetiser_helper_scmst_copy_allowed;
                    config.mtu    = media->mtu;
                    config.time_before_ttp_to_tx = 0;//(5) * US_PER_MS;
                    config.tx_deadline = 0;//(1) * US_PER_MS;

                    media->tws_packetiser = TwsPacketiserMasterInit(&config);
                }
                else
                {
                    Transform packetiser = PanicNull(TransformPacketise(source, sink));
                    PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_CODEC, VM_TRANSFORM_PACKETISE_CODEC_SBC));
                    PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_MODE, VM_TRANSFORM_PACKETISE_MODE_RTP));
                    PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_MTU, (uint16)codec_settings->codecData.packet_size));
                    PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_SAMPLE_RATE, (uint16) codec_settings->rate));
                    PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_CPENABLE, (uint16) codec_settings->codecData.content_protection));

                    PanicFalse(TransformStart(packetiser));

                    media->packetiser = packetiser;
                }
            }
            break;

            case A2DP_SEID_APTXHD:
            case A2DP_SEID_APTX:
            {
                Transform packetiser = PanicNull(TransformPacketise(source, sink));

                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_CODEC, VM_TRANSFORM_PACKETISE_CODEC_APTX));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_MODE, VM_TRANSFORM_PACKETISE_MODE_RTP));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_MTU, (uint16)codec_settings->codecData.packet_size));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_SAMPLE_RATE, (uint16) codec_settings->rate));

                /* aptX HD needs a special configuration */
                if (codec_settings->seid == A2DP_SEID_APTXHD)
                {
                    PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_CPENABLE, 1));
                    PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_SCMS, 0));
                }
                else
                {
                    PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_CPENABLE, (uint16)codec_settings->codecData.content_protection));
                }

                PanicFalse(TransformStart(packetiser));

                media->packetiser = packetiser;
            }
            break;

            case A2DP_SEID_APTX_LOW_LATENCY:
            {
                Transform packetiser = PanicNull(TransformPacketise(source, sink));

                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_CODEC, VM_TRANSFORM_PACKETISE_CODEC_APTX));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_MODE, VM_TRANSFORM_PACKETISE_MODE_TWSPLUS));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_MTU, (uint16)codec_settings->codecData.packet_size));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_SAMPLE_RATE, (uint16) codec_settings->rate));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_CPENABLE, (uint16) codec_settings->codecData.content_protection));

                PanicFalse(TransformStart(packetiser));

                media->packetiser = packetiser;
            }
            break;

            case A2DP_SEID_APTXAD:
            {
                Transform packetiser = PanicNull(TransformPacketise(source, sink));

                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_RTP_SSRC, 0xAC));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_RTP_SSRC_HI, 0));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_CODEC, VM_TRANSFORM_PACKETISE_CODEC_APTX));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_MODE, VM_TRANSFORM_PACKETISE_MODE_TWSPLUS));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_MTU, (uint16)codec_settings->codecData.packet_size));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_SAMPLE_RATE, (uint16) codec_settings->rate));
                PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_CPENABLE, (uint16) codec_settings->codecData.content_protection));

                PanicFalse(TransformStart(packetiser));

                media->packetiser = packetiser;
            }
            break;

            default:
                Panic();
            break;
        }
    }
}

static void sco_chain_usb_connect(Sink sink, bool esco, bool wbs, uint16 size_warp, uint16 *warp)
{
    usb_config_t usb_config;
    UNUSED(sink);
    UNUSED(esco);
    UNUSED(wbs);
    UNUSED(warp);
    UNUSED(size_warp);

    DEBUG_LOG_INFO("AUDIO: sco_chain_usb_connect");

    if (!AUDIO_RUNDATA.media_focus)
    {
        /* permit audio to stream */
        OperatorFrameworkEnable(1);

        AUDIO_RUNDATA.sco_media.type = AUDIO_MEDIA_TYPE_AGHFP;
        AUDIO_RUNDATA.sco_media.sink = sink;
        AUDIO_RUNDATA.sco_media.source = StreamSourceFromSink(sink);
        AUDIO_RUNDATA.sco_media.rate = wbs?16000:8000;
        /****************************************************************************************************/
        /*                                    Create SCO Chain Operators                                    */
        /****************************************************************************************************/
        /* Create the operators for SCO chain */
        AUDIO_RUNDATA.sco_chain[USB_SCO_RX_WBS_DEC]          = PanicZero(OperatorsCreate(capability_id_wbs_receive, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        AUDIO_RUNDATA.sco_chain[USB_SCO_RX_IIR_RESAMPLER_OP] = PanicZero(OperatorsCreate(capability_id_iir_resampler, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        AUDIO_RUNDATA.sco_chain[USB_SCO_RX_USB_TX_OP]        = PanicZero(OperatorsCreate(capability_id_usb_audio_tx, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));

        AUDIO_RUNDATA.sco_chain[USB_SCO_TX_MIXER_OP]         = PanicZero(OperatorsCreate(capability_id_mixer, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        AUDIO_RUNDATA.sco_chain[USB_SCO_TX_IIR_RESAMPLER_OP] = PanicZero(OperatorsCreate(capability_id_iir_resampler, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        AUDIO_RUNDATA.sco_chain[USB_SCO_TX_SOURCE_SYNC_OP]   = PanicZero(OperatorsCreate(capability_id_source_sync, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        AUDIO_RUNDATA.sco_chain[USB_SCO_TX_WBS_ENC]          = PanicZero(OperatorsCreate(capability_id_wbs_send, OPERATOR_PROCESSOR_ID_0, operator_priority_high));
        AUDIO_RUNDATA.sco_chain[USB_SCO_TX_USB_AUDIO_RX_OP]  = PanicZero(OperatorsCreate(capability_id_usb_audio_rx, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        AUDIO_RUNDATA.sco_chain[USB_SCO_TX_AEC_REF_OP]       = PanicZero(OperatorsCreate(CAP_ID_AEC_REFERENCE, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY));
        AUDIO_RUNDATA.sco_chain[USB_SCO_TX_SPC_OP]           = PanicZero(OperatorsCreateWithSetup(CAP_ID_SWITCHED_PASSTHROUGH_CONSUMER, OPERATOR_PROCESSOR_ID_0, DEFAULT_OPERATOR_PRIORITY, &usb_sco_tx_spc_setup));

        /****************************************************************************************************/
        /*                                    Configure SCO Tx Operators                                    */
        /****************************************************************************************************/
        /* Configure USB Rx operator */
        usb_config.number_of_channels = 2;
        usb_config.sample_rate        = 48000;
        usb_config.sample_size        = 2;
        OperatorsConfigureUsbAudio(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_USB_AUDIO_RX_OP], usb_config);

        /* Configure USB Rx latencies. */
        OperatorsStandardSetTimeToPlayLatency(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_USB_AUDIO_RX_OP], TTP_LATENCY_IN_US(12));

        /* Configure mixer. */
        OperatorsMixerSetChannelsPerStream(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_MIXER_OP], 1, 1, 0);
        OperatorsMixerSetPrimaryStream(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_MIXER_OP], 1);
        OperatorsMixerSetNumberOfSamplesToRamp(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_MIXER_OP], 0);
        OperatorsStandardSetBufferSize(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_MIXER_OP], 2000);

        /* Configure iir_resampler operator */
        OperatorsResamplerSetConversionRate(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_IIR_RESAMPLER_OP], 48000, AUDIO_RUNDATA.sco_media.rate);

        /* Configure AEC reference */
        Source source_line_in_left = PanicNull(StreamAudioSource(AUDIO_HARDWARE_CODEC, AUDIO_INSTANCE_0, AUDIO_CHANNEL_A));
        Sink source_line_out_left = PanicNull(StreamAudioSink(AUDIO_HARDWARE_CODEC, AUDIO_INSTANCE_0, AUDIO_CHANNEL_A));
        StreamDisconnect(source_line_in_left, source_line_out_left);
        SourceConfigure(source_line_in_left, STREAM_CODEC_INPUT_RATE, AUDIO_RUNDATA.sco_media.rate);
        SinkConfigure(source_line_out_left, STREAM_CODEC_OUTPUT_RATE, AUDIO_RUNDATA.sco_media.rate);
        OperatorsAecSetSampleRate(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_AEC_REF_OP], AUDIO_RUNDATA.sco_media.rate, AUDIO_RUNDATA.sco_media.rate);

        StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_AEC_REF_OP], 1),source_line_out_left);
        StreamConnect(source_line_in_left,StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_AEC_REF_OP], 2));

        /* Configure source sync operator */
        OperatorsStandardSetSampleRate(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_SOURCE_SYNC_OP], 16000);
        OperatorsSourceSyncSetSinkGroups(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_SOURCE_SYNC_OP], 1, &sco_tx_source_sync_sink_config);
        OperatorsSourceSyncSetSourceGroups(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_SOURCE_SYNC_OP], 1, &sco_tx_source_sync_source_config);
        OperatorsSourceSyncSetRoutes(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_SOURCE_SYNC_OP], 1, &sco_tx_source_sync_route_config);

        if(AUDIO_RUNDATA.sco_media.rate == 16000)
        {
          SinkConfigure(AUDIO_RUNDATA.sco_media.sink, VM_SINK_SCO_SET_FRAME_LENGTH, MSBC_SCO_FRAME_LENGTH);
        }
        /****************************************************************************************************/
        /*                                    Configure SCO Rx Operators                                    */
        /****************************************************************************************************/
        OperatorsStandardSetTimeToPlayLatency(AUDIO_RUNDATA.sco_chain[USB_SCO_RX_WBS_DEC], TTP_LATENCY_IN_US(20));
        OperatorsStandardSetBufferSize(AUDIO_RUNDATA.sco_chain[USB_SCO_RX_WBS_DEC], TTP_BUFFER_SIZE);
        /* Configure iir_resampler operator */
        OperatorsResamplerSetConversionRate(AUDIO_RUNDATA.sco_chain[USB_SCO_RX_IIR_RESAMPLER_OP], AUDIO_RUNDATA.sco_media.rate, 16000);

        /* Config USB TX operator */
        usb_config.number_of_channels = 1;
        usb_config.sample_rate        = 16000;
        usb_config.sample_size        = 2;
        OperatorsConfigureUsbAudio(AUDIO_RUNDATA.sco_chain[USB_SCO_RX_USB_TX_OP], usb_config);
        OperatorsStandardSetBufferSizeWithFormat(AUDIO_RUNDATA.sco_chain[USB_SCO_RX_USB_TX_OP], TTP_BUFFER_SIZE, operator_data_format_pcm);
        /****************************************************************************************************/
        /*                                    Connect SCO Rx Chain                                          */
        /****************************************************************************************************/
        StreamDisconnect(AUDIO_RUNDATA.sco_media.source, NULL);
        StreamDisconnect(NULL, StreamUsbEndPointSink(end_point_iso_out));

        PanicNull(StreamConnect(AUDIO_RUNDATA.sco_media.source, StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_RX_WBS_DEC], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_RX_USB_TX_OP],0), StreamUsbEndPointSink(end_point_iso_out)));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_RX_WBS_DEC],0), StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_RX_IIR_RESAMPLER_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_RX_IIR_RESAMPLER_OP],0), StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_RX_USB_TX_OP], 0)));
        /****************************************************************************************************/
        /*                                    Connect SCO Tx Chain                                          */
        /****************************************************************************************************/
        /* Connect SCO Tx chain */
        StreamDisconnect(StreamUsbEndPointSource(end_point_iso_in), NULL);
        StreamDisconnect(NULL, AUDIO_RUNDATA.sco_media.sink);

        PanicNull(StreamConnect(StreamUsbEndPointSource(end_point_iso_in), StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_USB_AUDIO_RX_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_WBS_ENC],0), AUDIO_RUNDATA.sco_media.sink));

        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_USB_AUDIO_RX_OP],0), StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_MIXER_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_USB_AUDIO_RX_OP],1), StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_MIXER_OP], 1)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_MIXER_OP],0), StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_IIR_RESAMPLER_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_IIR_RESAMPLER_OP],0), StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_AEC_REF_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_AEC_REF_OP],0), StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_SOURCE_SYNC_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_AEC_REF_OP],3), StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_SPC_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_SOURCE_SYNC_OP],0), StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_WBS_ENC], 0)));

        /* Message the SPC operator */
        OperatorsSpcSelectPassthroughInput(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_SPC_OP], spc_op_consume_all_inputs);

        /* start the operators */
        PanicFalse(OperatorStartMultiple(USB_SCO_NUM_OPERATORS, AUDIO_RUNDATA.sco_chain, NULL));
    }
}

static void sco_chain_analog_connect(Sink sink, bool esco, bool wbs, uint16 size_warp, uint16 *warp)
{
    /*AudioPluginFeatures features = {0,0,0}; *//* no stereo or i2s output */
    UNUSED(sink);
    UNUSED(esco);
    UNUSED(wbs);
    UNUSED(warp);
    UNUSED(size_warp);

    AUDIO_DEBUG(("AUDIO: sco_chain_analog_connect\n"));

    if (!AUDIO_RUNDATA.media_focus)
    {
        /* permit audio to stream */
        OperatorFrameworkEnable(1);

        AUDIO_RUNDATA.sco_media.type = AUDIO_MEDIA_TYPE_AGHFP;
        AUDIO_RUNDATA.sco_media.sink = sink;
        AUDIO_RUNDATA.sco_media.source = StreamSourceFromSink(sink);
        AUDIO_RUNDATA.sco_media.rate = wbs?16000:8000;

        /* Create the operators for SCO chain */
        AUDIO_RUNDATA.sco_chain[ANALOG_SCO_TX_IIR_RESAMPLER_OP]  = PanicZero(VmalOperatorCreate(capability_id_iir_resampler));
        vmal_operator_keys_t operator_key;
        operator_key.key = OPERATOR_CREATE_KEY_SET_PRIORITY;
        operator_key.value = operator_priority_high;
        AUDIO_RUNDATA.sco_chain[ANALOG_SCO_TX_WBS_ENC_OP]        = PanicZero(VmalOperatorCreateWithKeys(capability_id_wbs_send, &operator_key, 1));

        AUDIO_RUNDATA.sco_chain[ANALOG_SCO_RX_WBS_DEC_OP]        = PanicZero(VmalOperatorCreate(capability_id_wbs_receive));
        AUDIO_RUNDATA.sco_chain[ANALOG_SCO_RX_IIR_RESAMPLER_OP]  = PanicZero(VmalOperatorCreate(capability_id_iir_resampler));

        /* Configure iir_resampler operator */
        OperatorsResamplerSetConversionRate(AUDIO_RUNDATA.sco_chain[ANALOG_SCO_TX_IIR_RESAMPLER_OP], 48000, AUDIO_RUNDATA.sco_media.rate);

        /* Get the input endpoints */
        Source source_line_in_left  = PanicNull(StreamAudioSource(AUDIO_HARDWARE_CODEC, AUDIO_INSTANCE_0, AUDIO_CHANNEL_A));
        Sink sink_line_out_left = PanicNull(StreamAudioSink(AUDIO_HARDWARE_CODEC, AUDIO_INSTANCE_0, AUDIO_CHANNEL_A));
        StreamDisconnect(source_line_in_left, sink_line_out_left);
        /* ...and configure the sample rate and gain for each channel */
        PanicFalse(SourceConfigure(source_line_in_left,  STREAM_CODEC_INPUT_RATE, DAC_SAMPLE_RATE));
        PanicFalse(SinkConfigure(sink_line_out_left, STREAM_CODEC_OUTPUT_RATE, DAC_SAMPLE_RATE));

        OperatorsResamplerSetConversionRate(AUDIO_RUNDATA.sco_chain[ANALOG_SCO_RX_IIR_RESAMPLER_OP], AUDIO_RUNDATA.sco_media.rate, 48000);
        /* connect operators */
        /* Connect SCO Rx chain */
        PanicNull(StreamConnect(AUDIO_RUNDATA.sco_media.source, StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[ANALOG_SCO_RX_WBS_DEC_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[ANALOG_SCO_RX_WBS_DEC_OP],0), StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[ANALOG_SCO_RX_IIR_RESAMPLER_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[ANALOG_SCO_RX_IIR_RESAMPLER_OP],0), sink_line_out_left));

        /* Connect SCO Tx chain */
        PanicNull(StreamConnect(source_line_in_left, StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[ANALOG_SCO_TX_IIR_RESAMPLER_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[ANALOG_SCO_TX_IIR_RESAMPLER_OP],0), StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[ANALOG_SCO_TX_WBS_ENC_OP], 0)));
        PanicNull(StreamConnect(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[ANALOG_SCO_TX_WBS_ENC_OP],0), AUDIO_RUNDATA.sco_media.sink));

        if(AUDIO_RUNDATA.sco_media.rate == 16000)
        {
          SinkConfigure(AUDIO_RUNDATA.sco_media.sink, VM_SINK_SCO_SET_FRAME_LENGTH, MSBC_SCO_FRAME_LENGTH);
        }

        SourceConfigure(AUDIO_RUNDATA.sco_media.source, VM_SOURCE_SCO_METADATA_ENABLE, SCO_METADATA_ENABLE);
        /* start the operators */
        PanicFalse(OperatorStartMultiple(ANALOG_SCO_NUM_OPERATORS, AUDIO_RUNDATA.sco_chain, NULL));
    }
}

static void usb_chain_destroy(media_t *media)
{
    Source usb_source;

    if(media->is_routed)
    {
        DEBUG_LOG_INFO("AUDIO: usb_chain_destroy");
        PanicFalse(OperatorStopMultiple(A2DP_USB_NUM_OPERATORS, media->operators, NULL)); /* stop chain */

        usb_source = StreamUsbEndPointSource(end_point_iso_in);

        StreamDisconnect(usb_source, 0);
        StreamConnectDispose(usb_source);

        StreamDisconnect(NULL, StreamSinkFromOperatorTerminal(media->operators[A2DP_USB_RX_OP], 0));

        SourceUnmap(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_SPLITTER_OP], 0));
        SourceUnmap(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_SPLITTER_OP], 1));

        StreamDisconnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_SPLITTER_OP], 0), NULL);
        StreamDisconnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_USB_SPLITTER_OP], 1), NULL);

        CustomOperatorDestroy(media->operators, A2DP_USB_NUM_OPERATORS); /* destroyOperators */

        media->is_routed  = FALSE;

        MessageCancelAll(&AUDIO_RUNDATA.taskData, MSG_SILENCE_DETECTION);
    }
}

static void aux_chain_destroy(media_t *media)
{
    if(media->is_routed)
    {
        DEBUG_LOG_INFO("AUDIO: aux_chain_destroy");
        PanicFalse(OperatorStopMultiple(A2DP_AUX_NUM_OPERATORS, media->operators, NULL)); /* stop chain */

        Source source_line_in_left = StreamAudioSource(AUDIO_HARDWARE_CODEC, AUDIO_INSTANCE_0, AUDIO_CHANNEL_SLOT_0 );//StreamSourceFromSink(StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_TPP_OP], 0));
        Source source_line_in_right = StreamAudioSource(AUDIO_HARDWARE_CODEC, AUDIO_INSTANCE_0, AUDIO_CHANNEL_SLOT_1 );// StreamSourceFromSink(StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_TPP_OP], 1));

        StreamDisconnect(source_line_in_left, 0);
        StreamDisconnect(source_line_in_right, 0);
        SourceClose(source_line_in_left);
        SourceClose(source_line_in_right);

        StreamDisconnect(NULL, StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_TPP_OP], 0));
        StreamDisconnect(NULL, StreamSinkFromOperatorTerminal(media->operators[A2DP_AUX_TPP_OP], 1));

        SourceUnmap(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_SPLITTER_OP], 0));
        SourceUnmap(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_SPLITTER_OP], 1));

        StreamDisconnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_SPLITTER_OP], 0), NULL);
        StreamDisconnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_AUX_SPLITTER_OP], 1), NULL);

        CustomOperatorDestroy(media->operators, A2DP_AUX_NUM_OPERATORS); /* destroyOperators */
        media->is_routed  = FALSE;
    }
}

static void i2s_chain_destroy(media_t *media)
{
    if(media->is_routed)
    {
        DEBUG_LOG_INFO("AUDIO: i2s_chain_destroy");
        PanicFalse(OperatorStopMultiple(A2DP_I2S_NUM_OPERATORS, media->operators, NULL)); /* stop chain */

        StreamDisconnect(NULL, StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_TPP_OP], 0));
        StreamDisconnect(NULL, StreamSinkFromOperatorTerminal(media->operators[A2DP_I2S_TPP_OP], 1));

        SourceUnmap(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_SPLITTER_OP], 0));
        SourceUnmap(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_SPLITTER_OP], 1));

        StreamDisconnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_SPLITTER_OP], 0), NULL);
        StreamDisconnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_I2S_SPLITTER_OP], 1), NULL);

        CustomOperatorDestroy(media->operators, A2DP_I2S_NUM_OPERATORS); /* destroyOperators */
        media->is_routed  = FALSE;
    }
}

static void spdif_chain_destroy(media_t *media)
{
    if(media->is_routed)
    {
        DEBUG_LOG_INFO("AUDIO: spdif_chain_destroy");
        PanicFalse(OperatorStopMultiple(A2DP_SPDIF_NUM_OPERATORS, media->operators, NULL)); /* stop chain */

        StreamDisconnect(NULL, StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_DECODE_OP], 0));
        StreamDisconnect(NULL, StreamSinkFromOperatorTerminal(media->operators[A2DP_SPDIF_DECODE_OP], 1));

        SourceUnmap(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_SPLITTER_OP], 0));
        SourceUnmap(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_SPLITTER_OP], 1));

        StreamDisconnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_SPLITTER_OP], 0), NULL);
        StreamDisconnect(StreamSourceFromOperatorTerminal(media->operators[A2DP_SPDIF_SPLITTER_OP], 1), NULL);

        CustomOperatorDestroy(media->operators, A2DP_SPDIF_NUM_OPERATORS); /* destroyOperators */
        media->is_routed  = FALSE;
    }
}


static void a2dp_chain_destroy(media_t *media)
{
    if(media->is_routed)
    {
        DEBUG_LOG_INFO("AUDIO: a2dp_chain_destroy");

        if(media->packetiser)
        {
            StreamDisconnect(media->source, 0);

            StreamDisconnect(0, media->sink);

            TransformStop(media->packetiser);
        }
        else if(media->tws_packetiser)
        {
            StreamDisconnect(media->source, 0);

            StreamDisconnect(0, media->sink);

            TwsPacketiserMasterDestroy(media->tws_packetiser);
        }
        else
        {
            MessageStreamTaskFromSink(media->sink, NULL);
            MessageStreamTaskFromSource(media->source, NULL);

            SourceUnmap(media->source);

            StreamDisconnect(media->source, 0);
            StreamDisconnect(0, media->sink);

            media->sink   = NULL;
            media->source = NULL;
        }

        memset(media->operators, 0, sizeof(Operator) * MAX_NUM_OPERATORS);
        media->packetiser = NULL;
        media->seid       = 0;
        media->is_routed  = FALSE;
    }
}

static void tone_chain_destroy(media_t *audioSourceMedia, media_t *toneMedia)
{
    uint16 volume;
    AUDIO_DEBUG(("AUDIO: tone_chain_destroy\n"));
    if(audioSourceMedia && toneMedia)
    {
        volume = Kymera_VolDbToGain(0);

        switch(audioSourceMedia->type)
        {
        case AUDIO_MEDIA_TYPE_USB:
            OperatorsVolumeSetAuxGain(audioSourceMedia->operators[A2DP_USB_VOL_CTRL_OP], volume);
            break;
        case AUDIO_MEDIA_TYPE_ANALOG:
            OperatorsVolumeSetAuxGain(audioSourceMedia->operators[A2DP_AUX_VOL_CTRL_OP], volume);
            break;
        case AUDIO_MEDIA_TYPE_I2S:
            OperatorsVolumeSetAuxGain(audioSourceMedia->operators[A2DP_I2S_VOL_CTRL_OP], volume);
            break;
        case AUDIO_MEDIA_TYPE_SPDIF:
            OperatorsVolumeSetAuxGain(audioSourceMedia->operators[A2DP_SPDIF_VOL_CTRL_OP], volume);
            break;
        default:
            break;
        }

        if (toneMedia->source)
        {
            MessageStreamTaskFromSource(toneMedia->source, NULL);
            StreamDisconnect(toneMedia->source, NULL);
            SourceClose(toneMedia->source);
        }

        PanicFalse(OperatorStopMultiple(TONE_NUM_OPERATORS, toneMedia->operators, NULL)); /* stop chain */
        CustomOperatorDestroy(toneMedia->operators, TONE_NUM_OPERATORS); /* destroyOperators */
        toneMedia->is_routed  = FALSE;
    }
}

static void sco_chain_usb_destroy(void)
{
    DEBUG_LOG_INFO("AUDIO: sco_chain_usb_destroy");

    PanicFalse(OperatorStopMultiple(USB_SCO_NUM_OPERATORS, AUDIO_RUNDATA.sco_chain, NULL)); /* stop chain */

    StreamDisconnect(StreamUsbEndPointSource(end_point_iso_in), 0);
    StreamDisconnect(0, StreamUsbEndPointSink(end_point_iso_out));

    Sink sink_line_out_left = StreamSinkFromSource(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_AEC_REF_OP], 1));
    Source source_line_in_left = StreamSourceFromSink(StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[USB_SCO_TX_AEC_REF_OP], 2));

    StreamDisconnect(source_line_in_left, 0);
    StreamDisconnect(0, sink_line_out_left);
    SourceClose(source_line_in_left);
    SinkClose(sink_line_out_left);

    CustomOperatorDestroy(AUDIO_RUNDATA.sco_chain, USB_SCO_NUM_OPERATORS); /* destroyOperators */

    VmalOperatorFrameworkEnableMainProcessor(FALSE); /* Power off DSP */
}

static void sco_chain_analog_destroy(void)
{
    AUDIO_DEBUG(("AUDIO: sco_chain_analog_destroy\n"));

    PanicFalse(OperatorStopMultiple(ANALOG_SCO_NUM_OPERATORS, AUDIO_RUNDATA.sco_chain, NULL)); /* stop chain */

    Sink source_line_out_left = StreamSinkFromSource(StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[ANALOG_SCO_RX_IIR_RESAMPLER_OP],0));
    Source source_line_in_left = StreamSourceFromSink(StreamSinkFromOperatorTerminal(AUDIO_RUNDATA.sco_chain[ANALOG_SCO_TX_IIR_RESAMPLER_OP], 0));

    StreamDisconnect(source_line_in_left, 0);
    StreamDisconnect(0, source_line_out_left);
    SourceClose(source_line_in_left);
    SinkClose(source_line_out_left);

    CustomOperatorDestroy(AUDIO_RUNDATA.sco_chain, ANALOG_SCO_NUM_OPERATORS); /* destroyOperators */

    VmalOperatorFrameworkEnableMainProcessor(FALSE); /* Power off DSP */
}

/****************************************************************************
NAME
    audio_internal_a2dp_disconnect(void)

DESCRIPTION
    Attempt to disconnect the A2DP audio.

RETURNS
    void
*/
static void audio_internal_a2dp_disconnect(void)
{
    if (AUDIO_RUNDATA.audio_routed == AUDIO_ROUTED_A2DP)
    {
        DEBUG_LOG_INFO("AUDIO: audio_internal_a2dp_disconnect");

        switch(AUDIO_RUNDATA.input_source)
        {
        case AUDIO_SOURCE_USB:
        {
            OperatorsSetSwitchedPassthruMode(AUDIO_RUNDATA.usb_media.operators[A2DP_USB_SPC_OP], spc_op_mode_consumer);

            usb_chain_destroy(&AUDIO_RUNDATA.usb_media);

            a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[0]);
            a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[1]);

            AUDIO_RUNDATA.audio_routed = AUDIO_ROUTED_NONE;
        }
        break;
        case AUDIO_SOURCE_ANALOG:
        {
            aux_chain_destroy(&AUDIO_RUNDATA.aux_media);

            a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[0]);
            a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[1]);

            AUDIO_RUNDATA.audio_routed = AUDIO_ROUTED_NONE;
        }
        break;
        case AUDIO_SOURCE_I2S:
        {
            i2s_chain_destroy(&AUDIO_RUNDATA.i2s_media);

            a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[0]);
            a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[1]);

            AUDIO_RUNDATA.audio_routed = AUDIO_ROUTED_NONE;
        }
        break;
        case AUDIO_SOURCE_SPDIF:
        {
            spdif_chain_destroy(&AUDIO_RUNDATA.spdif_media);

            a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[0]);
            a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[1]);

            AUDIO_RUNDATA.audio_routed = AUDIO_ROUTED_NONE;
        }
        break;
        default:
            PanicFalse(FALSE);
            break;
        }
    }
}
/****************************************************************************
NAME
    audio_init -

DESCRIPTION
    Initialises the audio section of code.

RETURNS
    void
*/
void audio_init(void)
{
    FILE_INDEX index = FILE_NONE;

    memset(&AUDIO_RUNDATA, 0, sizeof(AUDIO_DATA_T));

    AUDIO_RUNDATA.taskData.handler = audio_msg_handler;
    AUDIO_RUNDATA.a2dp_media[0].index = 0;
    AUDIO_RUNDATA.a2dp_media[1].index = 1;

    if(buttons_aux_cable_is_connected())
    {
        AUDIO_RUNDATA.input_source = AUDIO_SOURCE_ANALOG;
    }
    else
    {
        AUDIO_RUNDATA.input_source = AUDIO_SOURCE_USB;
    }

    /* Load aptx adaptive encoder to DSP */

    index = FileFind(FILE_ROOT, download_aptx_encoder, strlen(download_aptx_encoder));
    if(index == FILE_NONE) Panic();
    PanicZero(OperatorBundleLoad(index, AUDIO_CORE_0));

    index = FileFind(FILE_ROOT, download_aptx_hd_encoder, strlen(download_aptx_hd_encoder));
    if(index == FILE_NONE) Panic();
    PanicZero(OperatorBundleLoad(index, AUDIO_CORE_0));

    index = FileFind(FILE_ROOT, download_aptx_low_latency_encoder, strlen(download_aptx_low_latency_encoder));
    if(index == FILE_NONE) Panic();
    PanicZero(OperatorBundleLoad(index, AUDIO_CORE_0));

    index = FileFind(FILE_ROOT, download_aptx_ad_encoder, strlen(download_aptx_ad_encoder));
    if(index == FILE_NONE) Panic();
    PanicZero(OperatorBundleLoad(index, AUDIO_CORE_0));
}

/****************************************************************************
NAME
    audio_a2dp_connect -

DESCRIPTION
    Attempt to route the A2DP audio.

RETURNS
    void
*/
void audio_a2dp_connect(Sink sink, uint16 device_id, uint16 stream_id)
{
    DEBUG_LOG_INFO("AUDIO: audio_a2dp_connect device_id=%d, stream_id=%d", device_id, stream_id);
    UNUSED(sink);
    UNUSED(device_id);
    UNUSED(stream_id);

    Source source;
    a2dp_codec_settings *codec_settings = NULL;
    a2dpInstance *a2dp_ins0 = a2dp_get_instance_from_device_id(0);
    a2dpInstance *a2dp_ins1 = a2dp_get_instance_from_device_id(1);

    switch(AUDIO_RUNDATA.input_source)
    {
        case AUDIO_SOURCE_USB:
        {
            if(a2dp_ins0 && a2dp_ins1)
            {
                if(a2dp_get_state(a2dp_ins0) == A2DP_STATE_CONNECTED_MEDIA_STREAMING &&
                   a2dp_get_state(a2dp_ins1) == A2DP_STATE_CONNECTED_MEDIA_STREAMING)
                {
                    a2dp_codec_settings *device0_codec_settings = A2dpCodecGetSettings(a2dp_ins0->a2dp_device_id, a2dp_ins0->a2dp_stream_id);
                    a2dp_codec_settings *device1_codec_settings = A2dpCodecGetSettings(a2dp_ins1->a2dp_device_id, a2dp_ins1->a2dp_stream_id);

                    usb_chain_destroy(&AUDIO_RUNDATA.usb_media);

                    a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[0]);
                    a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[1]);

                    usb_chain_connect(&AUDIO_RUNDATA.usb_media, TRUE, device0_codec_settings);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.usb_media.operators[A2DP_USB_SPLITTER_OP], 0);
                    audio_configure_packetiser(source, a2dp_ins0->media_sink, &AUDIO_RUNDATA.a2dp_media[0], device0_codec_settings, a2dp_ins0->apple_airpods_pro2, TRUE);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.usb_media.operators[A2DP_USB_SPLITTER_OP], 1);
                    audio_configure_packetiser(source, a2dp_ins1->media_sink, &AUDIO_RUNDATA.a2dp_media[1], device1_codec_settings, a2dp_ins1->apple_airpods_pro2, TRUE);

                    if((device0_codec_settings->seid == device1_codec_settings->seid) && (device0_codec_settings->seid == A2DP_SEID_SBC))
                    {
                        sbc_encoder_params_t sbc_encoder_params;

                        sbc_encoder_params.channel_mode       = device0_codec_settings->channel_mode;
                        sbc_encoder_params.bitpool_size       = AUDIO_RUNDATA.bitpool;
                        sbc_encoder_params.sample_rate        = device0_codec_settings->rate;
                        sbc_encoder_params.number_of_subbands = (device0_codec_settings->codecData.format & 1) ? 8 : 4;;
                        sbc_encoder_params.number_of_blocks   = (((device0_codec_settings->codecData.format >> 4) & 3) + 1) * 4;
                        sbc_encoder_params.allocation_method  = (device0_codec_settings->codecData.format & 0x02)? sbc_encoder_allocation_method_snr:sbc_encoder_allocation_method_loudness;

                        OperatorsSbcEncoderSetEncodingParams(AUDIO_RUNDATA.usb_media.operators[A2DP_USB_ENCODER_OP], &sbc_encoder_params);
                    }
                    /* Finally start the operators */
                    PanicFalse(OperatorStartMultiple(A2DP_USB_NUM_OPERATORS, AUDIO_RUNDATA.usb_media.operators, NULL));

                    memory_free(device0_codec_settings);
                    memory_free(device1_codec_settings);
                }
                else
                {
                    return;
                }
            }
            else
            {
                if(a2dp_ins0 && a2dp_get_state(a2dp_ins0) == A2DP_STATE_CONNECTED_MEDIA_STREAMING)
                {
                    audio_internal_a2dp_disconnect();

                    codec_settings = A2dpCodecGetSettings(a2dp_ins0->a2dp_device_id, a2dp_ins0->a2dp_stream_id);

                    usb_chain_connect(&AUDIO_RUNDATA.usb_media, FALSE, codec_settings);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.usb_media.operators[A2DP_USB_SPLITTER_OP], 0);
                    audio_configure_packetiser(source, a2dp_ins0->media_sink, &AUDIO_RUNDATA.a2dp_media[0], codec_settings, a2dp_ins0->apple_airpods_pro2, FALSE);
                    /* Finally start the operators */
                    PanicFalse(OperatorStartMultiple(A2DP_USB_NUM_OPERATORS, AUDIO_RUNDATA.usb_media.operators, NULL));

                    memory_free(codec_settings);
                }
                else if(a2dp_ins1 && a2dp_get_state(a2dp_ins1) == A2DP_STATE_CONNECTED_MEDIA_STREAMING)
                {
                    audio_internal_a2dp_disconnect();

                    codec_settings = A2dpCodecGetSettings(a2dp_ins1->a2dp_device_id, a2dp_ins1->a2dp_stream_id);

                    usb_chain_connect(&AUDIO_RUNDATA.usb_media, FALSE, codec_settings);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.usb_media.operators[A2DP_USB_SPLITTER_OP], 0);
                    audio_configure_packetiser(source, a2dp_ins1->media_sink, &AUDIO_RUNDATA.a2dp_media[1], codec_settings, a2dp_ins1->apple_airpods_pro2, FALSE);
                    /* Finally start the operators */
                    PanicFalse(OperatorStartMultiple(A2DP_USB_NUM_OPERATORS, AUDIO_RUNDATA.usb_media.operators, NULL));

                    memory_free(codec_settings);
                }
                else
                {
                    return;
                }
            }
        }
        break;
        case AUDIO_SOURCE_ANALOG:
        {
            if(a2dp_ins0 && a2dp_ins1)
            {
                if(a2dp_get_state(a2dp_ins0) == A2DP_STATE_CONNECTED_MEDIA_STREAMING &&
                   a2dp_get_state(a2dp_ins1) == A2DP_STATE_CONNECTED_MEDIA_STREAMING)
                {
                    a2dp_codec_settings *device0_codec_settings = A2dpCodecGetSettings(a2dp_ins0->a2dp_device_id, a2dp_ins0->a2dp_stream_id);
                    a2dp_codec_settings *device1_codec_settings = A2dpCodecGetSettings(a2dp_ins1->a2dp_device_id, a2dp_ins1->a2dp_stream_id);

                    aux_chain_destroy(&AUDIO_RUNDATA.aux_media);

                    a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[0]);
                    a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[1]);

                    aux_chain_connect(&AUDIO_RUNDATA.aux_media, TRUE, device0_codec_settings);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.aux_media.operators[A2DP_AUX_SPLITTER_OP], 0);
                    audio_configure_packetiser(source, a2dp_ins0->media_sink, &AUDIO_RUNDATA.a2dp_media[0], device0_codec_settings, a2dp_ins0->apple_airpods_pro2, TRUE);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.aux_media.operators[A2DP_AUX_SPLITTER_OP], 1);
                    audio_configure_packetiser(source, a2dp_ins1->media_sink, &AUDIO_RUNDATA.a2dp_media[1], device1_codec_settings, a2dp_ins1->apple_airpods_pro2, TRUE);

                    if((device0_codec_settings->seid == device1_codec_settings->seid) &&
                       (device0_codec_settings->seid == A2DP_SEID_SBC))
                    {
                        if(device0_codec_settings->codecData.bitpool != device1_codec_settings->codecData.bitpool)
                        {
                            sbc_encoder_params_t sbc_encoder_params;

                            sbc_encoder_params.channel_mode       = device0_codec_settings->channel_mode;
                            sbc_encoder_params.bitpool_size       = AUDIO_RUNDATA.bitpool;
                            sbc_encoder_params.sample_rate        = device0_codec_settings->rate;
                            sbc_encoder_params.number_of_subbands = (device0_codec_settings->codecData.format & 1) ? 8 : 4;;
                            sbc_encoder_params.number_of_blocks   = (((device0_codec_settings->codecData.format >> 4) & 3) + 1) * 4;
                            sbc_encoder_params.allocation_method  = (device0_codec_settings->codecData.format & 0x02)? sbc_encoder_allocation_method_snr:sbc_encoder_allocation_method_loudness;

                            OperatorsSbcEncoderSetEncodingParams(AUDIO_RUNDATA.aux_media.operators[A2DP_AUX_ENCODER_OP], &sbc_encoder_params);
                        }
                    }
                    /* Finally start the operators */
                    PanicFalse(OperatorStartMultiple(A2DP_AUX_NUM_OPERATORS, AUDIO_RUNDATA.aux_media.operators, NULL));

                    memory_free(device0_codec_settings);
                    memory_free(device1_codec_settings);
                }
                else
                {
                    return;
                }
            }
            else
            {
                if(a2dp_ins0 && a2dp_get_state(a2dp_ins0) == A2DP_STATE_CONNECTED_MEDIA_STREAMING)
                {
                    audio_internal_a2dp_disconnect();

                    codec_settings = A2dpCodecGetSettings(a2dp_ins0->a2dp_device_id, a2dp_ins0->a2dp_stream_id);

                    aux_chain_connect(&AUDIO_RUNDATA.aux_media, FALSE, codec_settings);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.aux_media.operators[A2DP_AUX_SPLITTER_OP], 0);
                    audio_configure_packetiser(source, a2dp_ins0->media_sink, &AUDIO_RUNDATA.a2dp_media[0], codec_settings, a2dp_ins0->apple_airpods_pro2, FALSE);
                    /* Finally start the operators */
                    PanicFalse(OperatorStartMultiple(A2DP_AUX_NUM_OPERATORS, AUDIO_RUNDATA.aux_media.operators, NULL));

                    memory_free(codec_settings);
                }
                else if(a2dp_ins1 && a2dp_get_state(a2dp_ins1) == A2DP_STATE_CONNECTED_MEDIA_STREAMING)
                {
                    audio_internal_a2dp_disconnect();

                    codec_settings = A2dpCodecGetSettings(a2dp_ins1->a2dp_device_id, a2dp_ins1->a2dp_stream_id);

                    aux_chain_connect(&AUDIO_RUNDATA.aux_media, FALSE, codec_settings);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.aux_media.operators[A2DP_AUX_SPLITTER_OP], 0);
                    audio_configure_packetiser(source, a2dp_ins1->media_sink, &AUDIO_RUNDATA.a2dp_media[1], codec_settings, a2dp_ins1->apple_airpods_pro2, FALSE);
                    /* Finally start the operators */
                    PanicFalse(OperatorStartMultiple(A2DP_AUX_NUM_OPERATORS, AUDIO_RUNDATA.aux_media.operators, NULL));

                    memory_free(codec_settings);
                }
                else
                {
                    return;
                }
            }
        }
        break;

        case AUDIO_SOURCE_I2S:
        {
            if(a2dp_ins0 && a2dp_ins1)
            {
                if(a2dp_get_state(a2dp_ins0) == A2DP_STATE_CONNECTED_MEDIA_STREAMING &&
                   a2dp_get_state(a2dp_ins1) == A2DP_STATE_CONNECTED_MEDIA_STREAMING)
                {
                    a2dp_codec_settings *device0_codec_settings = A2dpCodecGetSettings(a2dp_ins0->a2dp_device_id, a2dp_ins0->a2dp_stream_id);
                    a2dp_codec_settings *device1_codec_settings = A2dpCodecGetSettings(a2dp_ins1->a2dp_device_id, a2dp_ins1->a2dp_stream_id);

                    i2s_chain_destroy(&AUDIO_RUNDATA.i2s_media);

                    a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[0]);
                    a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[1]);

                    i2s_chain_connect(&AUDIO_RUNDATA.i2s_media, TRUE, device0_codec_settings);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.i2s_media.operators[A2DP_I2S_SPLITTER_OP], 0);
                    audio_configure_packetiser(source, a2dp_ins0->media_sink, &AUDIO_RUNDATA.a2dp_media[0], device0_codec_settings, a2dp_ins0->apple_airpods_pro2, TRUE);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.i2s_media.operators[A2DP_I2S_SPLITTER_OP], 1);
                    audio_configure_packetiser(source, a2dp_ins1->media_sink, &AUDIO_RUNDATA.a2dp_media[1], device1_codec_settings, a2dp_ins1->apple_airpods_pro2, TRUE);

                    if((device0_codec_settings->seid == device1_codec_settings->seid) &&
                       (device0_codec_settings->seid == A2DP_SEID_SBC))
                    {
                        if(device0_codec_settings->codecData.bitpool != device1_codec_settings->codecData.bitpool)
                        {
                            sbc_encoder_params_t sbc_encoder_params;

                            sbc_encoder_params.channel_mode       = device0_codec_settings->channel_mode;
                            sbc_encoder_params.bitpool_size       = AUDIO_RUNDATA.bitpool;
                            sbc_encoder_params.sample_rate        = device0_codec_settings->rate;
                            sbc_encoder_params.number_of_subbands = (device0_codec_settings->codecData.format & 1) ? 8 : 4;;
                            sbc_encoder_params.number_of_blocks   = (((device0_codec_settings->codecData.format >> 4) & 3) + 1) * 4;
                            sbc_encoder_params.allocation_method  = (device0_codec_settings->codecData.format & 0x02)? sbc_encoder_allocation_method_snr:sbc_encoder_allocation_method_loudness;

                            OperatorsSbcEncoderSetEncodingParams(AUDIO_RUNDATA.i2s_media.operators[A2DP_I2S_ENCODER_OP], &sbc_encoder_params);
                        }
                    }
                    /* Finally start the operators */
                    PanicFalse(OperatorStartMultiple(A2DP_I2S_NUM_OPERATORS, AUDIO_RUNDATA.i2s_media.operators, NULL));

                    memory_free(device0_codec_settings);
                    memory_free(device1_codec_settings);
                }
                else
                {
                    return;
                }
            }
            else
            {
                if(a2dp_ins0 && a2dp_get_state(a2dp_ins0) == A2DP_STATE_CONNECTED_MEDIA_STREAMING)
                {
                    audio_internal_a2dp_disconnect();

                    codec_settings = A2dpCodecGetSettings(a2dp_ins0->a2dp_device_id, a2dp_ins0->a2dp_stream_id);

                    i2s_chain_connect(&AUDIO_RUNDATA.i2s_media, FALSE, codec_settings);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.i2s_media.operators[A2DP_I2S_SPLITTER_OP], 0);
                    audio_configure_packetiser(source, a2dp_ins0->media_sink, &AUDIO_RUNDATA.a2dp_media[0], codec_settings, a2dp_ins0->apple_airpods_pro2, FALSE);
                    /* Finally start the operators */
                    PanicFalse(OperatorStartMultiple(A2DP_I2S_NUM_OPERATORS, AUDIO_RUNDATA.i2s_media.operators, NULL));

                    memory_free(codec_settings);
                }
                else if(a2dp_ins1 && a2dp_get_state(a2dp_ins1) == A2DP_STATE_CONNECTED_MEDIA_STREAMING)
                {
                    audio_internal_a2dp_disconnect();

                    codec_settings = A2dpCodecGetSettings(a2dp_ins1->a2dp_device_id, a2dp_ins1->a2dp_stream_id);

                    i2s_chain_connect(&AUDIO_RUNDATA.i2s_media, FALSE, codec_settings);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.i2s_media.operators[A2DP_I2S_SPLITTER_OP], 0);
                    audio_configure_packetiser(source, a2dp_ins1->media_sink, &AUDIO_RUNDATA.a2dp_media[1], codec_settings, a2dp_ins1->apple_airpods_pro2, FALSE);
                    /* Finally start the operators */
                    PanicFalse(OperatorStartMultiple(A2DP_I2S_NUM_OPERATORS, AUDIO_RUNDATA.i2s_media.operators, NULL));

                    memory_free(codec_settings);
                }
                else
                {
                    return;
                }
            }
        }
        break;

        case AUDIO_SOURCE_SPDIF:
        {
            if(a2dp_ins0 && a2dp_ins1)
            {
                if(a2dp_get_state(a2dp_ins0) == A2DP_STATE_CONNECTED_MEDIA_STREAMING &&
                   a2dp_get_state(a2dp_ins1) == A2DP_STATE_CONNECTED_MEDIA_STREAMING)
                {
                    a2dp_codec_settings *device0_codec_settings = A2dpCodecGetSettings(a2dp_ins0->a2dp_device_id, a2dp_ins0->a2dp_stream_id);
                    a2dp_codec_settings *device1_codec_settings = A2dpCodecGetSettings(a2dp_ins1->a2dp_device_id, a2dp_ins1->a2dp_stream_id);

                    spdif_chain_destroy(&AUDIO_RUNDATA.spdif_media);
                    a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[0]);
                    a2dp_chain_destroy(&AUDIO_RUNDATA.a2dp_media[1]);

                    spdif_chain_connect(&AUDIO_RUNDATA.spdif_media, TRUE, device0_codec_settings);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.spdif_media.operators[A2DP_SPDIF_SPLITTER_OP], 0);
                    audio_configure_packetiser(source, a2dp_ins0->media_sink, &AUDIO_RUNDATA.a2dp_media[0], device0_codec_settings, a2dp_ins0->apple_airpods_pro2, TRUE);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.spdif_media.operators[A2DP_SPDIF_SPLITTER_OP], 1);
                    audio_configure_packetiser(source, a2dp_ins1->media_sink, &AUDIO_RUNDATA.a2dp_media[1], device1_codec_settings, a2dp_ins1->apple_airpods_pro2, TRUE);

                    if((device0_codec_settings->seid == device1_codec_settings->seid) && (device0_codec_settings->seid == A2DP_SEID_SBC))
                    {
                        if(device0_codec_settings->codecData.bitpool != device1_codec_settings->codecData.bitpool)
                        {
                            sbc_encoder_params_t sbc_encoder_params;

                            sbc_encoder_params.channel_mode       = device0_codec_settings->channel_mode;
                            sbc_encoder_params.bitpool_size       = AUDIO_RUNDATA.bitpool;
                            sbc_encoder_params.sample_rate        = device0_codec_settings->rate;
                            sbc_encoder_params.number_of_subbands = (device0_codec_settings->codecData.format & 1) ? 8 : 4;;
                            sbc_encoder_params.number_of_blocks   = (((device0_codec_settings->codecData.format >> 4) & 3) + 1) * 4;
                            sbc_encoder_params.allocation_method  = (device0_codec_settings->codecData.format & 0x02)? sbc_encoder_allocation_method_snr:sbc_encoder_allocation_method_loudness;

                            OperatorsSbcEncoderSetEncodingParams(AUDIO_RUNDATA.spdif_media.operators[A2DP_SPDIF_ENCODER_OP], &sbc_encoder_params);
                        }
                    }
                    /* Finally start the operators */
                    PanicFalse(OperatorStartMultiple(A2DP_SPDIF_NUM_OPERATORS, AUDIO_RUNDATA.spdif_media.operators, NULL));

                    memory_free(device0_codec_settings);
                    memory_free(device1_codec_settings);
                }
                else
                {
                    return;
                }
            }
            else
            {
                if(a2dp_ins0 && a2dp_get_state(a2dp_ins0) == A2DP_STATE_CONNECTED_MEDIA_STREAMING)
                {
                    audio_internal_a2dp_disconnect();

                    codec_settings = A2dpCodecGetSettings(a2dp_ins0->a2dp_device_id, a2dp_ins0->a2dp_stream_id);

                    spdif_chain_connect(&AUDIO_RUNDATA.spdif_media, FALSE, codec_settings);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.spdif_media.operators[A2DP_SPDIF_SPLITTER_OP], 0);
                    audio_configure_packetiser(source, a2dp_ins0->media_sink, &AUDIO_RUNDATA.a2dp_media[0], codec_settings, a2dp_ins0->apple_airpods_pro2, FALSE);
                    /* Finally start the operators */
                    PanicFalse(OperatorStartMultiple(A2DP_SPDIF_NUM_OPERATORS, AUDIO_RUNDATA.spdif_media.operators, NULL));

                    memory_free(codec_settings);
                }
                else if(a2dp_ins1 && a2dp_get_state(a2dp_ins1) == A2DP_STATE_CONNECTED_MEDIA_STREAMING)
                {
                    audio_internal_a2dp_disconnect();

                    codec_settings = A2dpCodecGetSettings(a2dp_ins1->a2dp_device_id, a2dp_ins1->a2dp_stream_id);

                    spdif_chain_connect(&AUDIO_RUNDATA.spdif_media, FALSE, codec_settings);

                    source = StreamSourceFromOperatorTerminal(AUDIO_RUNDATA.spdif_media.operators[A2DP_SPDIF_SPLITTER_OP], 0);
                    audio_configure_packetiser(source, a2dp_ins1->media_sink, &AUDIO_RUNDATA.a2dp_media[1], codec_settings, a2dp_ins1->apple_airpods_pro2, FALSE);
                    /* Finally start the operators */
                    PanicFalse(OperatorStartMultiple(A2DP_SPDIF_NUM_OPERATORS, AUDIO_RUNDATA.spdif_media.operators, NULL));

                    memory_free(codec_settings);
                }
                else
                {
                    return;
                }
            }
        }
        break;
        default:
            PanicFalse(FALSE);
        break;
    }

    AUDIO_RUNDATA.audio_routed = AUDIO_ROUTED_A2DP;
    /*
    MessageCancelAll(app_get_instance(), APP_AUDIO_CONNECTED_IND);
    MessageCancelAll(app_get_instance(), APP_AUDIO_DISCONNECTED_IND);
    MessageSendLater(app_get_instance(), APP_AUDIO_CONNECTED_IND, 0, AUDIO_CHAIN_UPDATE_EVENT_TIME);
    */
}

/****************************************************************************
NAME
    audio_a2dp_disconnect

DESCRIPTION
    Attempt to disconnect the A2DP audio.

RETURNS
    void
*/
void audio_a2dp_disconnect(uint16 device_id, Sink media_sink)
{
    DEBUG_LOG_INFO("AUDIO: audio_a2dp_disconnect device_id = %d", device_id);
    UNUSED(device_id);
    UNUSED(media_sink);

    MessageSendLater(&AUDIO_RUNDATA.taskData, MSG_DELAY_DISCONNECT_AUDIO, 0, 100);
    /*
    MessageCancelAll(app_get_instance(), APP_AUDIO_CONNECTED_IND);
    MessageCancelAll(app_get_instance(), APP_AUDIO_DISCONNECTED_IND);
    MessageSendLater(app_get_instance(), APP_AUDIO_DISCONNECTED_IND, 0, AUDIO_CHAIN_UPDATE_EVENT_TIME);
    */
}

/****************************************************************************
NAME
    audio_set_voip_music_mode

DESCRIPTION
    Set the audio mode in use (VOIP \ MUSIC).

RETURNS
    void
*/
void audio_set_voip_music_mode(AUDIO_VOIP_MUSIC_MODE_T mode)
{
    AUDIO_DEBUG(("AUDIO: Audio Mode [%d]\n", mode));
    AUDIO_RUNDATA.audio_voip_music_mode = mode;
}

/****************************************************************************
NAME
    audio_switch_voip_music_mode

DESCRIPTION
    Switch the audio mode in use (VOIP \ MUSIC).

RETURNS
    void
*/
void audio_switch_voip_music_mode(AUDIO_VOIP_MUSIC_MODE_T new_mode)
{
    if (states_get_state() == SOURCE_STATE_CONNECTED)
    {
        AUDIO_DEBUG(("AUDIO: Switch Audio Mode\n"));
        if ((new_mode == AUDIO_VOIP_MODE) && (AUDIO_RUNDATA.audio_voip_music_mode == AUDIO_MUSIC_MODE))
        {
            AUDIO_DEBUG(("AUDIO: Switch Audio Mode 0 %d %d\n", connection_mgr_is_aghfp_profile_enabled(), aghfp_get_number_connections()));
            if (connection_mgr_is_aghfp_profile_enabled() && aghfp_get_number_connections())
            {
                audio_set_voip_music_mode(AUDIO_VOIP_MODE);

                MessageSend(app_get_instance(), APP_AUDIO_SUSPEND, 0);

                MessageSendLater(app_get_instance(), APP_AUDIO_START, 0, 150);
            }
        }
        else if ((new_mode == AUDIO_MUSIC_MODE) && (AUDIO_RUNDATA.audio_voip_music_mode == AUDIO_VOIP_MODE))
        {
            if (connection_mgr_is_a2dp_profile_enabled() && a2dp_get_number_connections())
            {
                /* switch to MUSIC mode from VOIP mode */
                audio_set_voip_music_mode(AUDIO_MUSIC_MODE);

                if(audio_get_input_source() == AUDIO_SOURCE_USB && audio_get_usb_active_flag())
                {
                    MessageSend(app_get_instance(), APP_AUDIO_START, 0);
                }
                else
                {
                    MessageSend(app_get_instance(), APP_AUDIO_SUSPEND, 0);
                }

                /* make sure Voice Recognition is disabled as this can cause audio routing issues on the remote device */
                aghfp_send_voice_recognition(FALSE);
            }
        }
    }
}


/****************************************************************************
NAME
    audio_aghfp_connect

DESCRIPTION
    Attempt to route the AGHFP audio.

RETURNS
    void
*/
void audio_aghfp_connect(Sink sink, bool esco, bool wbs, uint16 size_warp, uint16 *warp)
{
    AUDIO_DEBUG(("AUDIO: audio_aghfp_connect\n"));
    if (AUDIO_RUNDATA.media_focus && AUDIO_RUNDATA.media_focus->type == AUDIO_MEDIA_TYPE_A2DP)
    {
        audio_internal_a2dp_disconnect();
    }

    if(AUDIO_RUNDATA.media_focus && AUDIO_RUNDATA.media_focus->type == AUDIO_MEDIA_TYPE_AGHFP)
    {
        return;
    }

    switch(AUDIO_RUNDATA.input_source)
    {
    case AUDIO_SOURCE_USB:
        sco_chain_usb_connect(sink, esco, wbs, size_warp, warp);
        break;
    case AUDIO_SOURCE_ANALOG:
        sco_chain_analog_connect(sink, esco, wbs, size_warp, warp);
        break;
    default:
        Panic();
        break;
    }

     AUDIO_RUNDATA.media_focus = &AUDIO_RUNDATA.sco_media;
}


/****************************************************************************
NAME
    audio_aghfp_disconnect

DESCRIPTION
    Attempt to disconnect the AGHFP audio.

RETURNS
    void
*/
void audio_aghfp_disconnect(void)
{
    AUDIO_DEBUG(("AUDIO: audio_aghfp_disconnect\n"));

    if (AUDIO_RUNDATA.media_focus && AUDIO_RUNDATA.media_focus->type == AUDIO_MEDIA_TYPE_AGHFP)
    {
        switch(AUDIO_RUNDATA.input_source)
        {
        case AUDIO_SOURCE_USB:
            sco_chain_usb_destroy();
            break;
        case AUDIO_SOURCE_ANALOG:
            sco_chain_analog_destroy();
            break;
        default:
            Panic();
            break;
        }

        AUDIO_RUNDATA.media_focus = NULL;
    }
}


/****************************************************************************
NAME
    audio_route_all

DESCRIPTION
    Route audio for all active connections.

RETURNS
    void
*/
void audio_route_all(void)
{
    /* AGHFP audio */
    aghfp_route_all_audio();

    /* A2DP audio */
    a2dp_route_all_audio();
}


/****************************************************************************
NAME
    audio_suspend_all

DESCRIPTION
    Suspend audio for all active connections.

RETURNS
    void
*/
void audio_suspend_all(void)
{
    /* AGHFP audio */
    aghfp_suspend_all_audio();

    /* A2DP audio */
    a2dp_suspend_all_audio();
}

/****************************************************************************
NAME
    audio_is_routable

DESCRIPTION
    if audio is ready for stream.

RETURNS
    void
*/
bool audio_is_routable(void)
{
    return a2dp_audio_is_routable();
}

/****************************************************************************
NAME
    audio_start_active_timer

DESCRIPTION
    Starts the audio active timer in USB mode if the USB audio interfaces are inactive.
    When the timer expires the Bluetooth audio links can be suspended as no USB audio will be active.

RETURNS
    void
*/
void audio_start_active_timer(void)
{

}


/****************************************************************************
NAME
    audio_a2dp_update_bitpool

DESCRIPTION
    Change the bitpool for the A2DP audio.

RETURNS
    void
*/
void audio_a2dp_update_bitpool(uint8 bitpool)
{
    AUDIO_RUNDATA.bitpool = bitpool;
}


/****************************************************************************
NAME
    audio_tone_play

DESCRIPTION
    Play voice prompt.

RETURNS
    void
*/
void audio_tone_play(AUDIO_TONE_T tone)
{
    if(!AUDIO_RUNDATA.tone_media.is_routed)
    {
        AUDIO_DEBUG(("AUDIO: audio_tone_play\n"));

        switch(AUDIO_RUNDATA.input_source)
        {
        case AUDIO_SOURCE_USB:
            if(AUDIO_RUNDATA.usb_media.is_routed)
            {
                tone_chain_connect(&AUDIO_RUNDATA.usb_media, &AUDIO_RUNDATA.tone_media, toneFiles[tone]);
            }
            break;
        case AUDIO_SOURCE_ANALOG:
            if(AUDIO_RUNDATA.aux_media.is_routed)
            {
                tone_chain_connect(&AUDIO_RUNDATA.aux_media, &AUDIO_RUNDATA.tone_media, toneFiles[tone]);
            }
            break;
        case AUDIO_SOURCE_I2S:
            if(AUDIO_RUNDATA.i2s_media.is_routed)
            {
                tone_chain_connect(&AUDIO_RUNDATA.i2s_media, &AUDIO_RUNDATA.tone_media, toneFiles[tone]);
            }
            break;
        case AUDIO_SOURCE_SPDIF:
            if(AUDIO_RUNDATA.spdif_media.is_routed)
            {
                tone_chain_connect(&AUDIO_RUNDATA.spdif_media, &AUDIO_RUNDATA.tone_media, toneFiles[tone]);
            }
            break;
        }
    }
}

/****************************************************************************
NAME
    audio_tone_stop

DESCRIPTION
    stop tone.

RETURNS
    void
*/
void audio_tone_stop(void)
{
    if(AUDIO_RUNDATA.tone_media.is_routed)
    {
        switch(AUDIO_RUNDATA.input_source)
        {
        case AUDIO_SOURCE_USB:
            if(AUDIO_RUNDATA.usb_media.is_routed)
            {
                tone_chain_destroy(&AUDIO_RUNDATA.usb_media, &AUDIO_RUNDATA.tone_media);
            }
            break;
        case AUDIO_SOURCE_ANALOG:
            if(AUDIO_RUNDATA.aux_media.is_routed)
            {
                tone_chain_destroy(&AUDIO_RUNDATA.aux_media, &AUDIO_RUNDATA.tone_media);
            }
            break;
        case AUDIO_SOURCE_I2S:
            if(AUDIO_RUNDATA.i2s_media.is_routed)
            {
                tone_chain_destroy(&AUDIO_RUNDATA.i2s_media, &AUDIO_RUNDATA.tone_media);
            }
            break;
        case AUDIO_SOURCE_SPDIF:
            if(AUDIO_RUNDATA.spdif_media.is_routed)
            {
                tone_chain_destroy(&AUDIO_RUNDATA.spdif_media, &AUDIO_RUNDATA.tone_media);
            }
            break;
        }
    }
}

/******************************************************************************
NAME
    audio_set_a2dp_conn_delay

DESCRIPTION
    Helper function to set the a2dp commection delay

RETURNS
    void
*/
void audio_set_a2dp_conn_delay(bool a2dpConnDelay)
{
    AUDIO_RUNDATA.audio_a2dp_connection_delay  = a2dpConnDelay;
}

/******************************************************************************
NAME
    audio_get_a2dp_conn_delay

DESCRIPTION
    Helper function to get the a2dp commection delay

RETURNS
    TRUE, if the A2DP connection delay is set,
    FALSE, if otherwise.
*/
bool audio_get_a2dp_conn_delay(void)
{
    return AUDIO_RUNDATA.audio_a2dp_connection_delay;
}

/******************************************************************************
NAME
    audio_get_voip_music_mode(void)

DESCRIPTION
    Helper function to get the a2dp voip music mode.

RETURNS
        The current audio mode which is active .
        0 = AUDIO_MUSIC_MODE,
        1 = AUDIO_VOIP_MODE
*/
AUDIO_VOIP_MUSIC_MODE_T audio_get_voip_music_mode(void)
{
    return AUDIO_RUNDATA.audio_voip_music_mode;
}

/******************************************************************************
NAME
audio_get_audio_routed.

DESCRIPTION
    Helper function to get the audio routed types

RETURNS
    If the audio is routed either through A2DP or AGHFP else none.
    0 = AUDIO_ROUTED_NONE,
    1 = AUDIO_ROUTED_A2DP,
    2 = AUDIO_ROUTED_AGHFP
*/
AUDIO_ROUTED_T audio_get_audio_routed(void)
{
    return AUDIO_RUNDATA.audio_routed;
}

/******************************************************************************
NAME
    audio_set_aghfp_conn_delay

DESCRIPTION
    Helper function to set the a2dp connection delay

RETURNS
    void
*/
void audio_set_aghfp_conn_delay(bool aghfpConnDelay)
{
    AUDIO_RUNDATA.audio_aghfp_connection_delay = aghfpConnDelay;
}
/******************************************************************************
NAME
    audio_get_aghfp_conn_delay

DESCRIPTION
    Helper function to get the aghfp commection delay

RETURNS
    void
*/
bool audio_get_aghfp_conn_delay(void)
{
    return AUDIO_RUNDATA.audio_aghfp_connection_delay;
}

/******************************************************************************
NAME
    audio_set_usb_active_flag

DESCRIPTION
    Helper function to set the Audio usb active flag

RETURNS
    void
*/
void audio_set_usb_active_flag(bool usbactive)
{
    AUDIO_RUNDATA.audio_usb_active = usbactive;
}

/****************************************************************************
NAME
    audio_get_usb_active_flag

DESCRIPTION
    Helper function to get the Audio usb active flag

RETURNS
    The pointer to the structure audio_instance_tag.
*/
bool audio_get_usb_active_flag(void)
{
    return AUDIO_RUNDATA.audio_usb_active;
}

/****************************************************************************
NAME
    audioSetConnectedAudioInstance -

DESCRIPTION
    Set the connected audio instance.

RETURNS
    input source type
*/
AUDIO_SOURCE_T audio_get_input_source(void)
{
    return AUDIO_RUNDATA.input_source;
}

/****************************************************************************
NAME
    audio_set_input_source -

DESCRIPTION
    Set input source.

RETURNS
    None
*/
void audio_set_input_source(AUDIO_SOURCE_T input_source)
{
    AUDIO_RUNDATA.input_source = input_source;
    AUDIO_DEBUG(("AUDIO: new input_source = %d\n", input_source));
}

/****************************************************************************
NAME
    audio_is_routing -

DESCRIPTION
    if audio is routing

RETURNS
    None
*/
bool audio_is_routing(void)
{
    if(AUDIO_RUNDATA.audio_routed != AUDIO_ROUTED_NONE)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

/****************************************************************************
NAME
    audio_get_codec -

DESCRIPTION
    get codec of streaming.

RETURNS
    None
*/
void audio_get_codec(uint8 *seid, uint8 *quality)
{
    *seid = 0xFF;
    *quality = 0xFF;

    if(AUDIO_RUNDATA.media_focus)
    {
        switch(AUDIO_RUNDATA.media_focus->seid)
        {
        case A2DP_SEID_SBC:
            *seid = A2DP_SEID_SBC;
            break;
        case A2DP_SEID_APTX_LOW_LATENCY:
            *seid = A2DP_SEID_APTX_LOW_LATENCY;
            break;
        default:
            break;
        }
    }
}

/******************************************************************************
NAME
audio_mute_speaker.

DESCRIPTION
    Set speaker to mute

RETURNS
    None
*/
void audio_mute_speaker(bool mute)
{
    if((AUDIO_RUNDATA.audio_routed == AUDIO_ROUTED_A2DP) && (audio_get_input_source() == AUDIO_SOURCE_USB))
    {
        OperatorsVolumeMute(AUDIO_RUNDATA.usb_media.operators[A2DP_USB_VOL_CTRL_OP], mute);
    }
}
